This notebook provides supplementary material as well as the complete code for reproducing the toyworld, executing all estimators and evaluating these estimators. The toyworld is based on a semi-synthetic data generated process. For this, census data from Germany on a 100m*100m regular grid has been used, which can be downloaded here. Each element in this grid is expressed as a tile. For computation purposes only a small area of Germany was used for the toyworld, namely the area of Munich and its near surroundings. This focus area includes 160,000 tiles. The code for clipping this specific area can be found here, which is also part of this research repository. For a mobile phone population the regular census population values are used. To mimic the mobile phone population of one mobile network operator (MNO) the population is reduced to about a third.

Furthermore, custom functions have been built and are used throughout this notebook, which can be found here. In near time, these will be released within an R-package.

The notebook has three main parts: Toyworld Generation, Estimation, Evaluation.

1 General Setup: Loading Packages and Custom Functions

# Data manipulation
library(tidyverse)
library(data.table) 

# Spatial operations
library(sf)
library(raster)
library(furrr)
library(stars)

# Matrix operations
library(Matrix)

# MNO data handling and propagation model setup
# Credits to Prof. Martijn Tennekes https://github.com/mtennekes/mobloc
library(mobloc)

# Comparison of 2d histograms (Kantorovitch Wasserstein distance a.k.a. Earth Movers distance)
# Credits to Prof. Stefano Gualandi https://cran.r-project.org/web/packages/SpatialKWD/SpatialKWD.pdf
library(SpatialKWD)

# Output organisation and plotting support
library(ggthemes)
library(viridis)
library(ggrepel)
library(ggpointdensity)
library(grid)
library(gridExtra)
library(knitr)
library(DT)

# seed for reproducibility
set.seed(42)


# Loading Custom functions
source("Code/pipeline functions.R")

2 Toyworld Generation

This part is concerned with generating a toyworld that is based on the above specified focus area. After specifying the population aspect of the toyworld, the radio network will be specified.

2.1 Toyworld Generation: Population

This subchapter defines the generation of a population. As mentioned above the focus area is zoomed into the area of Munich and its near surroundings. The following chunk specifies the necessary objects to continue with the creation of a radio network in this focus area.

# data read in
munich.raw <- readRDS("Data/munich.rds")

# define raster object from focus area
munich.raster <- rasterFromXYZ(munich.raw, crs = st_crs(3035)$proj4string)

# define empty list object where all GTP objects will be stored
munich <- NULL

# define sf version of raster object
munich$area.sf <- munich.raster %>%
  st_as_stars() %>%
  st_as_sf() %>%
  dplyr::select(tile.id, pop, elevation) %>%
  mutate(type = "NA") # only necessary if different tile types can be defined (urban, rural, etc...)

# regular dataframe version
munich$area.df <- munich$area.sf %>%
  st_drop_geometry()

# unionized version of focus area
munich$area.union <- munich$area.sf %>%
  st_union()

# bounding box coordinates of focus area
munich$area.bbox <- munich$area.union %>%
  st_bbox(crs = sf::st_crs(3035))

# specify raster object and tile id number
munich$area.raster <- munich.raster %>%
  raster(., layer = "tile.id")

# specify raster object and elevation value of each tile (here considered as constant)
munich$area.elevation <- munich.raster %>%
  raster(., layer = "elevation")

# number of tiles
munich$area.params[["tile.num"]] <- length(munich$area.df$tile.id)

# size of tiles
munich$area.params[["base.tile.size"]] <- as.numeric(sqrt(st_area(munich$area.sf[1,])))

# stroing everything in area object
area <- munich
# adjustable break points for map categories
breaks <- c(0, 2, 5, 10, 20, 50, 100, 200, 350, Inf)
# plot map and print
area$area.sf %>% 
  mutate(pop.cat = cut(pop, breaks = breaks, dig.lab = 7, right = F)) %>% 
  map_density(data = ., var = "pop.cat", label = "GTP")
Spatial population density of the ground truth population

Figure 2.1: Spatial population density of the ground truth population

# summary descriptives of GTP
pop_summary_results(area$area.sf) %>% 
  dplyr::select(n.tiles = n.type, mean.pop, sd.pop, min.pop, max.pop, sum.pop)
## # A tibble: 1 x 6
##   n.tiles mean.pop sd.pop min.pop max.pop sum.pop
##     <dbl>    <dbl>  <dbl>   <dbl>   <dbl>   <dbl>
## 1  160000     4.24   13.5       0     329  677741
# ECCDF and ECDF of GTP
(density_plots(area$area.df))
## Warning in mask$eval_all_mutate(quo): NaNs produced
## Warning: Removed 192 rows containing missing values (geom_point).
## Warning: Transformation introduced infinite values in continuous y-axis
Distribution of the ground truth population

Figure 2.2: Distribution of the ground truth population

2.2 Toyworld Generation: Radio network

This subchapter refers to the development of a radio network within our focus area. Developing the radio network is heavily dependent on the mobloc package, which is promoted through the European Statistical System. However, certain functions have been adjusted to the authors needs.

In general, the mobloc package allows to define many parameters, however, if they are not defined, default parameters, set by the package, are used. This makes it very easy to implement as much / as little information one has on a certain network and always making it work. The following adjustable parameters and default values are provided through the package for creating a radio network and modelling the signal strength in a focus area:

# possible mobloc parameters
mobloc_param()
## $W
## [1] 10
## 
## $W_small
## [1] 5
## 
## $ple
## [1] 3.75
## 
## $ple_small
## [1] 6
## 
## $ple_0
## [1] 3.5
## 
## $ple_1
## [1] 4
## 
## $midpoint
## [1] -92.5
## 
## $steepness
## [1] 0.2
## 
## $range
## [1] 10000
## 
## $range_small
## [1] 1000
## 
## $height
## [1] 30
## 
## $height_small
## [1] 8
## 
## $tilt
## [1] 5
## 
## $beam_v
## [1] 9
## 
## $beam_h
## [1] 65
## 
## $azim_dB_back
## [1] -30
## 
## $elev_dB_back
## [1] -30
## 
## $sig_d_th
## [1] 0.005
## 
## $max_overlapping_cells
## [1] 100
## 
## $TA_step
## [1] 78.12
## 
## $TA_max
## [1] 1282
## 
## $TA_buffer
## [1] 1
## 
## attr(,"class")
## [1] "mobloc_param"

This toyworld contains a radio network with three layers (Macro, Meso and Micro). The development of each layer starts with a hexagonal grid in which the points define tower locations. The hexagons have different sizes (i.e. tower distance) dependent on the layer and contain some randomness within the layer (jitter) to prevent estimation artifacts. Each layer spans over the complete focus area.

On each tower three directional antennas are placed that are directed in a 120° angle to each other. All layers contain a rotation parameter to prevent antennas of different layers broadcasting into the exact same direction, in reference to the focus area. No omnidirectional antennas are implemented in this toyworld, therefore, all mobloc parameters with the suffix “_small” are not used.

The antennas are specified with layer specific parameters (e.g. height, power, path loss exponent, etc.). All antennas are specified in a so called cellplan in which all parameters are nested/asjusted. When the cellplan is completed the antenna specific broadcasting profile (i.e. cell profile) is estimated and projected onto the focus area. The function compute_sig_strength() computes the distance, signal strength and signal dominance between any tile and any antenna. Furthermore, a minimum parameter is implemented that defines the minimum signal dominance value an antenna-tile relationship needs to have in order to be considered “covered”. For the following estimators, it needs to be assured that every tile is sufficiently covered, meaning, that there is at least one antenna-tile relationship that has a signal dominance value higher than the minimum threshold.

# specify parameters of each cell
MA.cell.param.mobloc <- mobloc_param(W = 5, # Power in Watts
                                     range = 10000, # maximum coverage range
                                     ple = 3.4, # Path loss exponent
                                     height = 10, # height of the antenna
                                     midpoint = -85, # midpoint parameter of the logistic function for signal dominance
                                     steepness = 0.15, # steepness parameter of the logistic function for signal dominance
                                     sig_d_th = 0.05) # dominance minimum threshold 

ME.cell.param.mobloc <- mobloc_param(W = 50, range = 3500, ple = 3.8, height = 10,
                                     midpoint = -85, steepness = 0.3, sig_d_th = 0.05)

MI.cell.param.mobloc <- mobloc_param(W = 1, range = 3500, ple = 4, height = 6,
                                     midpoint = -85, steepness = 0.4, sig_d_th = 0.05)

# create dataframe for theoretical signal strength distribution
param.df <- tibble(cell.kind = c("MA", "ME", "MI"),
                   label = c("Macro", "Meso", "Micro"),
                   W = c(MA.cell.param.mobloc$W, ME.cell.param.mobloc$W, MI.cell.param.mobloc$W),
                   ple = c(MA.cell.param.mobloc$ple, ME.cell.param.mobloc$ple, MI.cell.param.mobloc$ple),
                   range = c(MA.cell.param.mobloc$range, ME.cell.param.mobloc$range, MI.cell.param.mobloc$range),
                   midpoint = c(MA.cell.param.mobloc$midpoint, ME.cell.param.mobloc$midpoint, MI.cell.param.mobloc$midpoint),
                   steepness = c(MA.cell.param.mobloc$steepness, ME.cell.param.mobloc$steepness, MI.cell.param.mobloc$steepness),
                   dominance.th = c(MA.cell.param.mobloc$sig_d_th,
                                    ME.cell.param.mobloc$sig_d_th,
                                    MI.cell.param.mobloc$sig_d_th))

# reduced data frame of theoretical signal strength distribution
param.df.reduced <- param.df %>% 
  dplyr::select(cell.kind, dominance.th)

# theoretical signal strength parameter plots
sig.pram.plots <- sig_param_plots(param.df = param.df, range.max = 20000, base_size = 11)

# print
(a <- ggpubr::as_ggplot(sig.pram.plots$final))
Theoretical radio network parameters for each layer

Figure 2.3: Theoretical radio network parameters for each layer

# save
ggsave("Plots/coverage.diag.png", a, device = "png", width = 10)

set.seed(100)

# create tower positions with attached cells
MA.cells.unparam <- create_cells(area.sf = area$area.sf, # focus area
                                 tower.dist = 8500, # tower distance
                                 rotation.deg = 0, # rotation parameter
                                 jitter = 1000, # amount of jitter in meters
                                 small = FALSE, # directional cell
                                 subscript = "MA", # layer label
                                 seed = 3)

ME.cells.unparam <- create_cells(area.sf = area$area.sf,
                                 tower.dist = 3500, rotation.deg = 35,
                                 jitter = 700, small = FALSE,
                                 subscript = "ME", seed = 7)

MI.cells.unparam <- create_cells(area.sf = area$area.sf,
                                 tower.dist = 10000, rotation.deg = 60,
                                 jitter = 2000, small = FALSE,
                                 subscript = "MI", seed = 10)


# create the cellplan and validate it with the specified parameters
MA.cellplan.val <- create_cellplan(area.sf = area$area.sf,  
                                   area.bbox = area$area.bbox, 
                                   area.elevation = area$area.elevation,
                                   cells.unparam = MA.cells.unparam,
                                   cell.param.mobloc = MA.cell.param.mobloc)

ME.cellplan.val <- create_cellplan(area.sf = area$area.sf,
                                   area.bbox = area$area.bbox,
                                   area.elevation = area$area.elevation,
                                   cells.unparam = ME.cells.unparam,
                                   cell.param.mobloc = ME.cell.param.mobloc)

MI.cellplan.val <- create_cellplan(area.sf = area$area.sf,
                                   area.bbox = area$area.bbox,
                                   area.elevation = area$area.elevation,
                                   cells.unparam = MI.cells.unparam,
                                   cell.param.mobloc = MI.cell.param.mobloc)
# cellplans need to be made valid!

cellplan.combined <- bind_rows(as_tibble(MA.cellplan.val$cellplan.val),
                               as_tibble(ME.cellplan.val$cellplan.val),
                               as_tibble(MI.cellplan.val$cellplan.val)) %>% 
  mutate(cell.kind = substr(cell, 1, 2)) %>% 
  left_join(param.df.reduced, by = "cell.kind") # join dominance threshold to use later in create_strength_llh()

# to join variable dominance.th later on
cellplan.combined.reduced <- cellplan.combined %>% 
  dplyr::select(cell, dominance.th)


# compute signal strength and device to cell association
MA.signal.strength <- compute_sig_strength(cp = MA.cellplan.val$cellplan.val, 
                                           raster = area$area.raster, 
                                           param = MA.cellplan.val$cell.param.mobloc, 
                                           elevation = area$area.elevation)

ME.signal.strength <- compute_sig_strength(cp = ME.cellplan.val$cellplan.val,
                                           raster = area$area.raster,
                                           param = ME.cellplan.val$cell.param.mobloc,
                                           elevation = area$area.elevation)

MI.signal.strength <- compute_sig_strength(cp = MI.cellplan.val$cellplan.val,
                                           raster = area$area.raster,
                                           param = MI.cellplan.val$cell.param.mobloc,
                                           elevation = area$area.elevation)

# create signal strength object of all cells
signal.strength.comb.dt <- rbindlist(list(MA.signal.strength,
                                          ME.signal.strength,
                                          MI.signal.strength))

  


signal.strength.summary.helper <- signal.strength.comb.dt %>%
  as_tibble() %>%
  mutate(tile.id = as.character(rid)) %>%
  mutate(cell.kind = substr(cell, 1, 2)) %>%
  mutate(cell.chr = as.character(cell)) %>%
  left_join(cellplan.combined.reduced, by = c("cell.chr" = "cell")) %>% 
  filter(!s < dominance.th) # filter rows out that are below the set dominance threshold

signal.strength.summary <- signal.strength.summary.helper %>% 
  group_by(tile.id) %>%
  mutate(max.dBm = max(dBm),
         max.s = max(s),
         min.dist = min(dist)) %>%
  ungroup()


# identify the cell-tile relations with maximum signal dominance and identify tiles that are not covered sufficiently
signal.dom <- signal.strength.summary %>% 
  distinct(tile.id, max.s) %>%
  left_join(signal.strength.summary, by = c("tile.id", "max.s" = "s")) %>% 
  dplyr::select(tile.id, max.s, cell, cell.kind) %>% 
  mutate(tile.id = as.integer(tile.id)) %>% 
  full_join(area$area.sf, by = "tile.id") %>% 
  mutate(missing = case_when(is.na(max.s) ~ 1,
                             TRUE ~ 0))

# how many tiles are not sufficiently covered
paste0("Number of tiles which do not reach the signal dominance threshold of: " , sum(signal.dom$missing))
## [1] "Number of tiles which do not reach the signal dominance threshold of: 0"
# paste0("Number of tiles which do not reach the signal dominance threshold of ", sig_d_th, ": " , sum(signal.dom$missing))

The following chunk is merely executed to develop a smoother visualization of the radio cell coverage profiles. For this, the signal strength is calculated on a finer grid 25m*25m. This finer grid is only relevant for this chunk and the following contour visualizations. The contours are interpolated based on the tiles’ signal dominance values and the tile centroids.

# specifiy how much finer the grid should be
finer.grid.factor <- 2
finer.grid.tile.id <- c(1:640000) # adjust to grid factor

# disaggregate the regular grid based on the finer.grid.factor
area$area.raster.finer <- disaggregate(area$area.raster, finer.grid.factor) %>% 
  setValues(., finer.grid.tile.id)

# disaggregate the elevation grid based on the finer.grid.factor
area$area.elevation.finer <- disaggregate(area$area.elevation, finer.grid.factor)

# transform raster back to sf
area$area.finer.grid.sf <- st_as_sf(st_as_stars(area$area.raster.finer))

# compute signal strength and device to cell association for finer grid for each layer
MA.signal.strength.finer <- compute_sig_strength(cp = MA.cellplan.val$cellplan.val, 
                                                 raster = area$area.raster.finer, 
                                                 param = MA.cellplan.val$cell.param.mobloc, 
                                                 elevation = area$area.elevation.finer)

ME.signal.strength.finer <- compute_sig_strength(cp = ME.cellplan.val$cellplan.val,
                                                 raster = area$area.raster.finer,
                                                 param = ME.cellplan.val$cell.param.mobloc,
                                                 elevation = area$area.elevation.finer)

MI.signal.strength.finer <- compute_sig_strength(cp = MI.cellplan.val$cellplan.val,
                                                 raster = area$area.raster.finer,
                                                 param = MI.cellplan.val$cell.param.mobloc,
                                                 elevation = area$area.elevation.finer)

signal.strength.comb.dt.finer.grid <- rbindlist(list(MA.signal.strength.finer,
                                                     ME.signal.strength.finer,
                                                     MI.signal.strength.finer))


# calculate tile centroid for each tile on the finer grid
area.reduced.contour <- area$area.finer.grid.sf %>% 
  dplyr::select(tile.id.num = tile.id) %>% 
  mutate(lon = map_dbl(geometry, ~st_centroid(.x)[[1]]),
         lat = map_dbl(geometry, ~st_centroid(.x)[[2]]))

# set the coordinate reference system
crs.set <- st_crs(area.reduced.contour)

# compute the antenna specfic broadcasting profile contour
# define two categories: contour of signal dominance values below or equal to 0.5 and above 0.5 resulting later in two contour lines per antenna
contour.data.raw <- signal.strength.comb.dt.finer.grid %>%
  as_tibble() %>%
  mutate(tile.id.num = as.numeric(rid)) %>%
  mutate(cell.chr = as.character(cell)) %>%
  left_join(cellplan.combined, by = c("cell.chr" = "cell")) %>% 
  filter(!s < dominance.th) %>% 
  mutate(s.discrete = case_when(s <= 0.5 ~ 1,
                                s > 0.5 ~ 2)) %>% # define categoristation of contours
  dplyr::select(cell, cell.kind, x.tow = x, y.tow = y, direction, tile.id.num, s, s.discrete) %>% 
  left_join(area.reduced.contour, by = c("tile.id.num")) %>% 
  st_as_sf(coords = c("lon", "lat"), crs = crs.set) 

# split according to layer
contour.data.raw.split <- contour.data.raw %>% 
  st_drop_geometry() %>% 
  split(.$cell.kind) %>% 
  map(~distinct(., cell, x.tow, y.tow))

# summarise antenna specific contour as polygon and then compute the convex hull to result in simple contour line
contour.data.summary <- contour.data.raw %>% 
  dplyr::select(cell, cell.kind, s.discrete, s) %>% 
  dplyr::group_by(cell, cell.kind, s.discrete) %>%
  dplyr::summarise() %>%
  st_convex_hull()
(contour.complete.MA <- contour.data.summary %>% 
  filter(cell.kind == "MA" & s.discrete == "1") %>% 
  ggplot() +
  geom_sf(data = area$area.union) +
  geom_sf(color = "red", fill = "transparent") +
  geom_point(data = contour.data.raw.split$MA, aes(x = x.tow, y = y.tow), color = "black", shape = 2) +
  theme_minimal() +
  labs(x = "", y = ""))
Coverage Contour of Macro cells, showing the area with less or equal than 0.5 signal dominance

Figure 2.4: Coverage Contour of Macro cells, showing the area with less or equal than 0.5 signal dominance

ggsave("Plots/contour.complete.MA.finer.png", contour.complete.MA, device = "png")
## Saving 7 x 5 in image
(contour.complete.ME <- contour.data.summary %>% 
  filter(cell.kind == "ME" & s.discrete == "1") %>% 
  ggplot() +
  geom_sf(data = area$area.union) +
  geom_sf(color = "red", fill = "transparent") +
  geom_point(data = contour.data.raw.split$ME, aes(x = x.tow, y = y.tow), color = "blue", shape = 2) +
  theme_minimal() +
  labs(x = "", y = ""))
Coverage Contour of Meso cells, showing the area with less or equal than 0.5 signal dominance

Figure 2.5: Coverage Contour of Meso cells, showing the area with less or equal than 0.5 signal dominance

ggsave("Plots/contour.complete.ME.finer.png", contour.complete.ME, device = "png")
## Saving 7 x 5 in image
(contour.complete.MI <- contour.data.summary %>% 
  filter(cell.kind == "MI" & s.discrete == "1") %>% 
  ggplot() +
  geom_sf(data = area$area.union) +
  geom_sf(color = "red", fill = "transparent") +
  geom_point(data = contour.data.raw.split$MI, aes(x = x.tow, y = y.tow), color = "green", shape = 2) +
  theme_minimal() +
  labs(x = "", y = ""))
Coverage Contour of Micro cells, showing the area with less or equal than 0.5 signal dominance

Figure 2.6: Coverage Contour of Micro cells, showing the area with less or equal than 0.5 signal dominance

ggsave("Plots/contour.complete.MI.finer.png", contour.complete.MI, device = "png")
## Saving 7 x 5 in image
(contour.certain <- contour.data.summary %>% 
  filter(str_detect(cell, "MA.12.|ME.100.|MI.18.")) %>% 
  ggplot() +
  geom_sf(data = area$area.union) +
  geom_sf(aes(color = factor(s.discrete)), fill = "transparent") +
  geom_point(data = contour.data.raw.split$MA, aes(x = x.tow, y = y.tow), color = "black", shape = 2) +
  geom_point(data = contour.data.raw.split$ME, aes(x = x.tow, y = y.tow), color = "blue", shape = 2) +
  geom_point(data = contour.data.raw.split$MI, aes(x = x.tow, y = y.tow), color = "green", shape = 2) +
  scale_color_discrete("Signal dom.", breaks = c("1", "2"), labels = c("0.05 <= s < 0.5",
                                                                      "s >= 0.5")) +
  theme_minimal() +
  labs(x = "", y = ""))
Coverage Contour of a sample cell of each layer, showing the discretized signal dominance (cell profile)

Figure 2.7: Coverage Contour of a sample cell of each layer, showing the discretized signal dominance (cell profile)

ggsave("Plots/hull.certain.finer.png", contour.certain, device = "png")
## Saving 7 x 5 in image

The following chunks visualize different views on antenna coverage, specific for each layer.

# maximum of two rows per tile, tiles with only one row, mean they are only covered by one cell.kind
signal.strength.summary.ck <- signal.strength.summary.helper %>% 
  group_by(tile.id, cell.kind) %>%
  summarise(cell.count = n(),
            max.dBm = max(dBm),
            max.s = max(s),
            min.dist = min(dist)) %>% 
  mutate(cell.count.complete = sum(cell.count),
         max.dBm.complete = max(max.dBm),
         max.s.complete = max(max.s), 
         min.dist.complete = max(min.dist)) %>% 
  ungroup() %>% 
  pivot_longer(cols = -c(tile.id, cell.kind),
               names_to = "kind", 
               values_to = "values") # possibly different pivot to get "kind" into multiple variables
## `summarise()` has grouped output by 'tile.id'. You can override using the `.groups` argument.
# histogram cells
(tile.coverage.hist <- signal.strength.summary.ck %>%
  filter(str_detect(kind, pattern = "count")) %>% 
  mutate(cat = case_when(str_detect(kind, pattern = "complete") ~ "complete",
                         TRUE ~ paste0(cell.kind))) %>% 
  distinct(tile.id, cat, .keep_all = T) %>% 
  dplyr::select(-cell.kind) %>% 
  ggplot() +
  geom_histogram(aes(values), binwidth = 1) +
  # scale_x_continuous(breaks = seq(0, 12, 1)) + # respecify to be dynamic
  facet_grid(~cat) +
  labs(title = "Number of cells a tile is covered by",
       y = "Tile count",
       x = "Number of cells"))
The number of cells a tile is covered by, in total and per cell layer

Figure 2.8: The number of cells a tile is covered by, in total and per cell layer

coverage.map.dom.df.MA <- signal.strength.summary.ck %>%
  filter(str_detect(kind, pattern = "max.s")) %>% 
  mutate(cat = case_when(str_detect(kind, pattern = "complete") ~ "complete",
                         TRUE ~ paste0(cell.kind))) %>% 
  filter(cat == "MA") %>% 
  dplyr::select(-cell.kind) %>% 
  mutate(tile.id.num = as.numeric(tile.id)) %>% 
  full_join(area$area.sf, by = c("tile.id.num" = "tile.id")) %>%  # change later on
  mutate(missing = case_when(is.na(values) ~ 1,
                             TRUE ~ 0)) %>% 
  st_as_sf()

coverage.map.dom.df.ME <- signal.strength.summary.ck %>%
  filter(str_detect(kind, pattern = "max.s")) %>% 
  mutate(cat = case_when(str_detect(kind, pattern = "complete") ~ "complete",
                         TRUE ~ paste0(cell.kind))) %>% 
  filter(cat == "ME") %>% 
  dplyr::select(-cell.kind) %>% 
  mutate(tile.id.num = as.numeric(tile.id)) %>% 
  full_join(area$area.sf, by = c("tile.id.num" = "tile.id")) %>%  # change later on
  mutate(missing = case_when(is.na(values) ~ 1,
                             TRUE ~ 0)) %>% 
  st_as_sf()

coverage.map.dom.df.MI <- signal.strength.summary.ck %>%
  filter(str_detect(kind, pattern = "max.s")) %>% 
  mutate(cat = case_when(str_detect(kind, pattern = "complete") ~ "complete",
                         TRUE ~ paste0(cell.kind))) %>% 
  filter(cat == "MI") %>% 
  dplyr::select(-cell.kind) %>% 
  mutate(tile.id.num = as.numeric(tile.id)) %>% 
  full_join(area$area.sf, by = c("tile.id.num" = "tile.id")) %>%  # change later on
  mutate(missing = case_when(is.na(values) ~ 1,
                             TRUE ~ 0)) %>% 
  st_as_sf()

coverage.map.dom.df.complete <- signal.strength.summary.ck %>%
  filter(str_detect(kind, pattern = "max.s")) %>% 
  mutate(cat = case_when(str_detect(kind, pattern = "complete") ~ "complete",
                         TRUE ~ paste0(cell.kind))) %>% 
  filter(cat == "complete") %>% 
  distinct(tile.id, cat, .keep_all = T) %>% 
  dplyr::select(-cell.kind) %>% 
  mutate(tile.id.num = as.numeric(tile.id)) %>% 
  full_join(area$area.sf, by = c("tile.id.num" = "tile.id")) %>%  # change later on
  mutate(missing = case_when(is.na(values) ~ 1,
                             TRUE ~ 0)) %>% 
  st_as_sf()
(coverage.map.MA <- coverage.map.dom.df.MA %>% 
    ggplot() +
    geom_sf(aes(fill = values), color = "transparent") +
    geom_point(data = as_tibble(MA.cellplan.val$cellplan.val), aes(x, y),
               shape = 2, color = "#F8766D") +
    scale_fill_gradient(low = "white", high = "black", na.value = "red", 
                        limits = c(0, 1)) +
    labs(title = "MA Coverage", 
         fill = "Signal Dominance",
         x = "",
         y = "") +
    theme_minimal() +
    theme(plot.margin = unit(c(0, 0, 0, 0), "cm"),
          axis.title.x = element_blank(),
          axis.text.x = element_blank(),
          axis.ticks.x = element_blank(),
          axis.title.y = element_blank(),
          axis.text.y = element_blank(),
          axis.ticks.y = element_blank(),
          legend.position = "none"))
Actual coverage heatmap for Macro cells

Figure 2.9: Actual coverage heatmap for Macro cells

(coverage.map.ME <- coverage.map.dom.df.ME %>% 
    ggplot() +
    geom_sf(aes(fill = values), color = "transparent") +
    geom_point(data = as_tibble(ME.cellplan.val$cellplan.val), aes(x, y),
               shape = 2, color = "#00BFC4") +
    scale_fill_gradient(low = "white", high = "black", na.value = "red", 
                        limits = c(0, 1)) +
    labs(title = " ME Coverage", 
         fill = "Signal Dominance",
         x = "",
         y = "") +
    theme_minimal() +
    theme(plot.margin = unit(c(0, 0, 0, 0), "cm"),
          legend.position = "none",
          axis.title.x = element_blank(),
          axis.text.x = element_blank(),
          axis.ticks.x = element_blank(),
          axis.title.y = element_blank(),
          axis.text.y = element_blank(),
          axis.ticks.y = element_blank()))
Actual coverage heatmap for Meso cells

Figure 2.10: Actual coverage heatmap for Meso cells

(coverage.map.MI <- coverage.map.dom.df.MI %>% 
    ggplot() +
    geom_sf(aes(fill = values), color = "transparent") +
    geom_point(data = as_tibble(MI.cellplan.val$cellplan.val), aes(x, y),
               shape = 2, color = "#00BFC4") +
    scale_fill_gradient(low = "white", high = "black", na.value = "red", 
                        limits = c(0, 1)) +
    labs(title = " MI Coverage", 
         fill = "Signal Dominance",
         x = "",
         y = "") +
    theme_minimal() +
    theme(plot.margin = unit(c(0, 0, 0, 0), "cm"),
          legend.position = "none",
          axis.title.x = element_blank(),
          axis.text.x = element_blank(),
          axis.ticks.x = element_blank(),
          axis.title.y = element_blank(),
          axis.text.y = element_blank(),
          axis.ticks.y = element_blank()))
Actual coverage heatmap for Micro cells

Figure 2.11: Actual coverage heatmap for Micro cells

(coverage.map.complete <- coverage.map.dom.df.complete %>% 
    ggplot() +
    geom_sf(aes(fill = values), color = "transparent") +
    geom_point(data = cellplan.combined, aes(x, y, color = cell.kind),
               shape = 2) +
    scale_fill_gradient(low = "white", high = "black", na.value = "red", 
                        limits = c(0, 1)) +
    labs(title = "Complete coverage", 
         color = "Tower kind",
         fill = "Signal Dominance",
         x = "",
         y = "") +
    theme_minimal() +
    theme(axis.title.x = element_blank(),
          axis.text.x = element_blank(),
          axis.ticks.x = element_blank(),
          axis.title.y = element_blank(),
          axis.text.y = element_blank(),
          axis.ticks.y = element_blank()))
Actual coverage heatmap for all cells

Figure 2.12: Actual coverage heatmap for all cells

coverage.map.dom.df.binary <- signal.strength.summary.ck %>%
  filter(str_detect(kind, pattern = "max.s"),
         !str_detect(kind, pattern = "complete")) %>%
  group_by(tile.id) %>%
  # mutate(values = round(values, 2)) %>%
  filter(values == max(values)) %>%
  ungroup() %>%
  mutate(tile.id.num = as.numeric(tile.id)) %>%
  full_join(area$area.sf, by = c("tile.id.num" = "tile.id")) %>%  # change later on
  mutate(missing = case_when(is.na(values) ~ 1,
                             TRUE ~ 0)) %>%
  st_as_sf()
(coverage.map.binary <- coverage.map.dom.df.binary %>%
    ggplot() +
    geom_sf(aes(fill = cell.kind), color = "transparent", alpha = 0.3, show = FALSE) +
    geom_point(data = cellplan.combined, aes(x, y, color = cell.kind),
               shape = 2) +
    labs(title = "Maximum coverage categorized by cell layer",
         color = "Cell kind",
         x = "",
         y = "") +
    theme_minimal() +
    theme(axis.title.x = element_blank(),
          axis.text.x = element_blank(),
          axis.ticks.x = element_blank(),
          axis.title.y = element_blank(),
          axis.text.y = element_blank(),
          axis.ticks.y = element_blank()))

2.3 Toyworld Generation: Device to cell association

This subchapter refers to the final task in the toyworld generation: the device to cell association. Here we bring the population with the network together and stochastically assign the mobile phone population in each tile to the respective antennas. The result is the so-called c-vector, which describes the number of mobile phones assigned to each cell. The basis parameter for this assignment is the signal dominance which is normalized in the form of a conditional probability. These conditional probabilities describe the elements of the P.matrix, which is the main model to estimate (geolocation module).

# specify the option of differing parameters for sig_d_th and max_overlapping_cells depending on the cell type in custom create_strength_llh function

# Workaround: securing that sig_d_th and max_overlapping_cells are the same for each layer
signal.strength.llh.param <- list(sig_d_th = max(cellplan.combined.reduced$dominance.th),
                                  max_overlapping_cells = 100)

# defining the connection probability
signal.strength.llh.combined <- create_strength_llh(strength = signal.strength.comb.dt, 
                                                    param = signal.strength.llh.param) %>% 
  as_tibble() %>% 
  mutate(tile.id = rid) %>% 
  group_by(tile.id) %>%
  mutate(pij = smart_round(pag, 3)) %>% # round values to the third decimal and assuring that all columns (tiles) add up to 1 (column stocahsticity)
  ungroup() %>%
  left_join(area$area.df, by = "tile.id") %>% 
  mutate(coverage.kind = case_when(pop == 0 ~ "0 population",
                                   pij == 1 ~ "covered completely by one antenna",
                                   pij > 0 & pij < 1 ~ "covered by multiple antennas",
                                   pij == 0 ~ "tile covered unsufficiently")) %>% 
  dplyr::select(-pag)

# aggregating and specifying the tiles that are uncovered (if there are some)
tiles.cat <- signal.strength.llh.combined %>% 
  filter(!pij == 0) %>% 
  dplyr::select(tile.id, coverage.kind) %>% 
  group_by(tile.id) %>% 
  summarise(count = n())

# how many tiles are not sufficiently covered
missings <- anti_join(area$area.df, tiles.cat, by = "tile.id") # implement non zero pop
paste("Number of tiles which are unsufficiently covered:", length(missings$tile.id))
## [1] "Number of tiles which are unsufficiently covered: 0"
# covered only by one tile
C.vec.fixed.helper <- signal.strength.llh.combined %>% 
  filter(coverage.kind == "covered completely by one antenna") %>%
  dplyr::select(tile.id, cell, pop)

# One object where tiles are covered by multiple cells
C.vec.multiple.helper <- signal.strength.llh.combined %>% 
  filter(coverage.kind == "covered by multiple antennas") %>% 
  group_split(tile.id) 

# Calculate the number of cores for parallelized process
no_cores <- min(availableCores() - 1,4)
plan(multisession, workers = no_cores)

# Sampling mobile phones within tiles to cells depending on connection probability
# parallelized process
C.vec.multiple <- C.vec.multiple.helper %>% 
  future_map(~sample(x = .$cell, mean(.$pop),
                     replace = T, prob = .$pij), 
             .options = furrr_options(seed = T), .progress = T) %>% 
  future_map(as_tibble, .id = "tile.id", .progress = T) %>% 
  future_map(~group_by(., value), .progress = T) %>% 
  future_map(~summarise(., pop.count.rand = n(), .groups = "drop"), 
             .progress = T)

# pulling all c-vec helper objects together and develop final c-vec dataframe
C.vec.df <- C.vec.multiple %>% 
  bind_rows() %>% 
  dplyr::select(cell = value, pop = pop.count.rand) %>% 
  bind_rows(C.vec.fixed.helper) %>% 
  group_by(cell) %>% 
  summarise(phones.sum = sum(pop))
# log scale breaks for background grid
minor.breaks <- rep(1:9, 21) * (10^rep(-10:10, each = 9))

# develop c-vec dataset for plotting eccdf and ecdf
c.vec.density.data <- C.vec.df %>% 
    mutate(c.plot = phones.sum + 1) %>%  
    arrange(c.plot) %>%  
    mutate(prob = 1 / n()) %>%  
    mutate(cum.prob = cumsum(prob)) %>%  
    mutate(cum.prob.comp = 1 - cum.prob) %>%  
    dplyr::select(cum.prob.comp, c.plot) 

# ECCDF plot c-vec
c.vec.eccdf <- c.vec.density.data %>% 
    ggplot() +
    geom_point(aes(x = c.plot, y = cum.prob.comp)) +
    scale_y_log10(labels = scales::trans_format("log10", 
                                                scales::math_format(10^.x)),
                  minor_breaks = minor.breaks) +
    scale_x_log10(labels = scales::trans_format("log10", 
                                                scales::math_format(10^.x)),
                  minor_breaks = minor.breaks) +
    annotation_logticks(sides = "lb") +
    labs(title = "Number of phones a cell has registered (ECCDF and ECDF)",
         y = "log(ECCDF)",
         x = "Number of phones")

# ECDF plot c-vec
c.vec.ecdf <- c.vec.density.data %>%   
  ggplot() + 
  geom_point(aes(x = c.plot, y = cum.prob.comp)) + 
  labs(title = "", y = "", x = "") +
  theme(legend.position = "none",
        plot.margin = unit(c(-0.5, 0, 0, -0.5), "cm")) 


(combined.c.vec <- c.vec.eccdf +
  annotation_custom(ggplotGrob(c.vec.ecdf), 
                    xmin = 0.2, xmax = 3, 
                    ymin = -2.8, ymax = -1))
## Warning: Transformation introduced infinite values in continuous y-axis
Distribution of the toyworld population

Figure 2.13: Distribution of the toyworld population

ggsave("Plots/histogram.c.vec.png", combined.c.vec, device = "png")
## Saving 7 x 5 in image
## Warning: Transformation introduced infinite values in continuous y-axis

In the following code chunk we are specifying the P matrix, with tiles as columns, antennas as rows and antenna-tile relationships as the elements. This matrix can be described as the connection probability. Each column adds up to 1, meaning it is column stochastic. This particular version of the connection probability can be described as exact, as it represents the same information which is used for the data generating mechanism in the development of the toyworld. We will define a copy of the P matrix, which will be called P* (P.star) and describes the modelling parameter necessary for all estimators, excluding the Voronoi estimators.

Furthermore, in the following code chunk we implement the process of consolidation a.k.a. the development of super tiles. These are tiles in the P matrix that are indistinguishable from each other based on their relationships to antennas (i.e. columns in the P matrix that are exactly the same). These tiles will yield in all estimators the exact same value, therefore, to reduce computational resources, they can be consolidated to supertiles and afterwards disaggregated again.

# develop long format of P matrix which also contains certain cellplan parameters and "zero elements"
# develop different versions of id variables (tile and antennas) for easier joining
P.long.complete.df <- full_join(signal.strength.llh.combined, C.vec.df, by = "cell") %>% 
  # dplyr::select(tile.id, pop, elevation, cell, type, dist, pij, phones.sum) %>% 
  dplyr::select(tile.id, pop, cell, type, dist, pij, phones.sum) %>% 
  mutate(tile.id.chr = tile.id) %>% 
  mutate(tile.id = factor(tile.id)) %>% 
  mutate(tile.id.num = as.numeric(tile.id)) %>% 
  mutate(cell.num = as.numeric(cell)) %>% 
  mutate(cell.chr = as.character(cell))

# Long format of P matrix with minimal variables and and unique rows
P.long.df <- P.long.complete.df %>% 
  dplyr::select(tile.id, tile.id.num, tile.id.chr, cell, cell.num, cell.chr, pij) %>% 
  distinct()

# Sparse matrix version of P matrix
P.mat <- sparseMatrix(i = P.long.df$cell.num, 
                      j = P.long.df$tile.id.num, 
                      x = P.long.df$pij)


# Workflow for consolidating regular tiles to supertiles based on P matrix
P.star.supertile.helper <- P.long.df %>% 
  dplyr::select(tile.id.chr, cell.chr, pij) %>% 
  filter(!pij == 0) %>% # assure that no "non-zero" elements are present
  group_by(tile.id.chr) %>% 
  arrange(cell.chr) %>% 
  mutate(cell.comp = paste0(cell.chr, collapse = ""),
         pij.comp = paste0(pij, collapse = "")) %>% # develop variables that contain character version of all cell names and all connection probabilities (order matters!!!)
  ungroup() %>% 
  group_by(cell.comp, pij.comp) %>% # group by these two variables to find tile that are identical in terms of the P matrix
  mutate(supertile.id = cur_group_id()) %>% # define the new supertile id
  ungroup() %>% 
  mutate(supertile.id = factor(supertile.id)) %>% 
  mutate(supertile.id.num = as.numeric(supertile.id)) %>% 
  mutate(supertile.id.chr = as.character(supertile.id)) %>% 
  right_join(P.long.df, by = c("tile.id.chr", "cell.chr")) %>% 
  dplyr::select(tile.id, tile.id.num, tile.id.chr, supertile.id, supertile.id.num, supertile.id.chr, 
                cell, cell.num, cell.chr, pij = pij.x) %>% 
  filter(!is.na(pij)) 

# develop joiner object for knowing which regular tiles are within a supertile
P.star.supertile.joiner <- P.star.supertile.helper %>%  
  distinct(tile.id.num, supertile.id, supertile.id.num, supertile.id.chr) %>% 
  arrange(tile.id.num) 

# define P matrix on the supertile level
P.star.supertile.long.df <- P.star.supertile.helper %>%
  distinct(supertile.id, supertile.id.num, supertile.id.chr, cell, cell.num, cell.chr, pij) %>% 
  arrange(cell.num)

# append supertile id to sim area base objects
area$area.sf.complete <- area$area.sf %>% 
  left_join(P.star.supertile.joiner, by = c("tile.id" = "tile.id.num"))
area$area.df.complete <- area$area.df %>% 
  left_join(P.star.supertile.joiner, by = c("tile.id" = "tile.id.num"))

3 Estimation

In the Estimation part we configure all necessary parameters for the defined estimators in the main paper. At the end, all final estimates are bounded together in one dataset on the tile level.

3.1 Estimation: Setup

Here we specify the connection probability models. In this case P = P*. Furthermore, we define the c-vector, which contains the number of mobile phones an antenna has connected to and the prior vector, which is a constant for each tile (here 1). For the following estimator functions the parameters are assumed to be in a datatable format or sparse Matrix via sparseMatrix.

# Modelling true P with P* (P.star)
P.star.dt <- P.long.df %>% 
  dplyr::select(i = cell.num, j = tile.id.num, pij) %>% 
  as.data.table()

# P* on the supertile level as sparse matrix version
P.star.supertile.spm <- sparseMatrix(i = P.star.supertile.long.df$cell.num, 
                                     j = P.star.supertile.long.df$supertile.id.num,
                                     x = P.star.supertile.long.df$pij)

# P* on the supertile level as datatable version
P.star.supertile.dt <- data.table(i = P.star.supertile.long.df$cell.num,
                                  j = P.star.supertile.long.df$supertile.id.num,
                                  pij = P.star.supertile.long.df$pij)

### C vector, adding antennas that have 0 phones to complete the vector, arranging it according to the antenna.ID and saving as vector
c.vec <- P.long.complete.df %>% 
  distinct(cell, cell.num, cell.chr, phones.sum) %>% 
  right_join(cellplan.combined, by = "cell") %>% 
  mutate(phones.sum = case_when(is.na(phones.sum) ~ 0,
                                TRUE ~ phones.sum)) %>% 
  arrange(cell.num)

# define c-vector in datatable version
c.vec.dt <- data.table(i = c.vec$cell.num,
                       c = c.vec$phones.sum)


### prior vectors
a.tile.helper <- P.long.complete.df %>% 
  mutate(a = 1) %>% 
  distinct(tile.id.num, a)

# define datatable version of constant prior
a.tile.dt <- data.table(j = a.tile.helper$tile.id.num, u = a.tile.helper$a)

# define constant prior on the supertile level
a.supertile.helper <- P.star.supertile.joiner %>% 
  group_by(supertile.id.num) %>% 
  summarise(a = n()) %>% 
  ungroup() %>% 
  distinct(supertile.id.num, a) %>% 
  deframe()

3.2 Estimation: Voronoi (Tower, Offset, Barycenter)

Here we define 3 options of the Voronoi estimator. An option always corresponds to the seed that is used to compute the Voronoi region. Each Voronoi estimator needs the c-vector and the cellplan as main parameters.

The first option is called “tower”, which disregards antennas and only uses tower locations as seeds. Therefore, the c-vector is aggregated on the tower level.

# Voronoi estimation with tower locations as seeds
VOR.tower <- VOR_est(area = area, 
                     cellplan.combined = cellplan.combined, 
                     signal.strength.comb.dt = signal.strength.comb.dt, 
                     C.vec.df = C.vec.df, 
                     seed = "tower")
## Warning: attribute variables are assumed to be spatially constant throughout all
## geometries

## Warning: attribute variables are assumed to be spatially constant throughout all
## geometries
VOR.tower.est <- VOR.tower$seed.voronoi.final %>% 
  rename(u.VOR.tower = u.VOR)

# print
VOR.tower$Voronoi.regions.plot
Voronoi regions with tower locations as seeds

Figure 3.1: Voronoi regions with tower locations as seeds

# save
ggsave("Plots/VOR_regions_tower.png", VOR.tower$Voronoi.regions.plot, device = "png")
## Saving 7 x 5 in image

The second option is defined on the antenna level and uses the location of each antenna (which is the same for each antenna of the same tower) plus an adjustable offset into the broadcasting direction of the respective antenna (which results in a different location for each antenna of the same tower). Here the offset is 10m.

# Voronoi estimation with cell locations + offset as seeds
VOR.offset <- VOR_est(area = area, 
                      cellplan.combined = cellplan.combined, 
                      signal.strength.comb.dt = signal.strength.comb.dt, 
                      C.vec.df = C.vec.df, 
                      seed = "cell.offset",
                      offset = 10) 
## Warning: attribute variables are assumed to be spatially constant throughout all
## geometries

## Warning: attribute variables are assumed to be spatially constant throughout all
## geometries
VOR.offset.est <- VOR.offset$seed.voronoi.final %>% 
  rename(u.VOR.offset = u.VOR)

# print
VOR.offset$Voronoi.regions.plot
Voronoi regions with antenna offsets as seeds

Figure 3.2: Voronoi regions with antenna offsets as seeds

# save
ggsave("Plots/VOR_regions_offset.png", VOR.offset$Voronoi.regions.plot, device = "png")
## Saving 7 x 5 in image

The third option is also on the antenna level and uses the barycenter of each antenna as seed location for the computation of Voronoi regions.

# Voronoi estimation with cell barycenter locations as seeds
VOR.barycenter <- VOR_est(area = area, 
                          cellplan.combined = cellplan.combined, 
                          signal.strength.comb.dt = signal.strength.comb.dt, 
                          C.vec.df = C.vec.df, 
                          seed = "cell.barycenter") 
## Warning in st_centroid.sf(.): st_centroid assumes attributes are constant over
## geometries of x
## Warning: attribute variables are assumed to be spatially constant throughout all
## geometries

## Warning: attribute variables are assumed to be spatially constant throughout all
## geometries
VOR.barycenter.est <- VOR.barycenter$seed.voronoi.final %>% 
  rename(u.VOR.barycenter = u.VOR)

# print
VOR.barycenter$Voronoi.regions.plot
Voronoi regions with antenna barycenters as seeds

Figure 3.3: Voronoi regions with antenna barycenters as seeds

# save
ggsave("Plots/VOR_regions_barycenter.png", VOR.barycenter$Voronoi.regions.plot, device = "png")
## Saving 7 x 5 in image

3.3 Estimation: Simple Bayes

The Simple Bayes (SB) estimator needs three parameters, the c-vector, an connection probability model (P*) and a prior vector. The following custom R-function (EM_est()) is basically the same for the MLE Poisson and SB estimator. The only difference is that the SB estimator uses only one (i.e. the first) iteration. Furthermore, this function contains a parameter ldt, which can potentially reduce the iterations until convergence. It clips all estimates below this value to exactly 0 as the original estimator could not reach exactly 0. Most parameters are assumed on the datatable format for computational speed purposes.

# caclulate SB estimator
SB.est <- EM_est(c.vec.dt = c.vec.dt, 
                 P.dt = P.star.dt, 
                 a.vec.dt = a.tile.dt, 
                 selected.range = 1,
                 n.iter = 1,
                 message = F, 
                 ldt = 10^-04) %>% 
  dplyr::rename(tile.id = j, prior.SB = i.u, u.SB = u1)

3.4 Estimation: MLE Poisson

The MLE Poisson estimator assumes the same parameters as the SB estimator, plus the number of iterations it should run. For convenience, EM_est() contains the argument selected.range, which gives the user the option to specify which iterations should be delivered in the output data frame.

# define number of iterations 
n.iter.MLE <- 200

# Time log
MLE.time <- system.time({
  # calculate MLE Poisson estimator
  MLE.est <- EM_est(c.vec.dt = c.vec.dt, 
                    P.dt = P.star.dt, 
                    a.vec.dt = a.tile.dt, 
                    selected.range = c(1:10, seq(20, 90, 10), seq(100, 200, 50)),
                    n.iter = n.iter.MLE,
                    message = F, 
                    ldt = 10^-04) %>% 
    rename_with(.fn = ~gsub("u", "u.MLE", x = .x, fixed = T), 
                .cols = starts_with("u")) %>% 
    rename(tile.id = j, prior.MLE = i.u)
})

One MLE iteration takes for this toyworld and the specified configurations on average ca. 1 seconds.

3.5 Estimation: DF

The DF estimator also assumes the same parameters as the SB and MLE-Poisson estimator, however, it is computed on the supertile level. The necessary matrix inversion is computed via the Moore Penrose inverse. Given the fact that the raw DF estimate can contain negative values, it is adjusted and renormalized in the following way: All estimate values below 1 are clipped at 1 and then one iteration with EM_est() is conducted in order to assure that the final estimates are of the same mass as the c-vector. Therefore, the DF estimator is the raw DF estimate after one iteration (DF1).

As another configuration, this notebook contains an adjusted MLE Poisson estimator, where the prior vector is the adjusted and renormalized DF estimate vector. This is coined in the following as DF200 which means that contains the estimates after 200 iterations.

# Time log
DF.raw.time <- system.time({
  # calculate raw DF estimates with supertiles
  DF.raw.est <- DF_est(c.vec.dt = c.vec.dt, 
                       P.star.spm = P.star.supertile.spm, 
                       a.supertile.vec = a.supertile.helper)
  # adjust raw DF estimate (clip)
  DF.raw.est.dt <- data.table(j = as.numeric(names(a.supertile.helper)),
                              u = DF.raw.est) %>%
    .[, u := fifelse(u < 1, 1, u)] # clip lower values than 1 to 1
})
## Registered S3 methods overwritten by 'lme4':
##   method                          from
##   cooks.distance.influence.merMod car 
##   influence.merMod                car 
##   dfbeta.influence.merMod         car 
##   dfbetas.influence.merMod        car
## Renormalizing with EM and bringing estimate on regular tile.id level
DF.est <- EM_est(c.vec.dt = c.vec.dt, 
                 P.dt = P.star.supertile.dt, 
                 a.vec.dt = DF.raw.est.dt,
                 selected.range = c(1:10, seq(20, 90, 10), seq(100, 200, 50)),
                 n.iter = n.iter.MLE,
                 message = F,
                 ldt = 10^-04) %>% 
  rename_with(.fn = ~gsub("u", "u.DF", x = .x, fixed = T), 
              .cols = starts_with("u")) %>% 
  rename(supertile.id.num = j, prior.DF = i.u) %>%
  right_join(area$area.df.complete, by = "supertile.id.num") %>% 
  group_by(supertile.id) %>% 
  mutate(across(starts_with("u"), ~ . / n())) %>% 
  ungroup() %>% 
  dplyr::select(tile.id, starts_with("u"))

Computing the raw DF-estimate (renormalized but without further MLE iterations) takes for this toyworld and the specified configurations ca. 2 seconds.

4 Evaluation

The last part of this notebook follows through with evaluating the different estimators based on the following indicators: 1d density, 2d density, Spatial density (i.e., geographical maps), KWD, and Convergence based on KWD.

4.1 Evaluation: Setup

# putting everything into an sf-dataframe together
final.estimates.sf <- area$area.sf %>% 
  left_join(VOR.tower.est, by = "tile.id") %>% 
  left_join(VOR.offset.est, by = "tile.id") %>% 
  left_join(VOR.barycenter.est, by = "tile.id") %>% 
  left_join(SB.est, by = "tile.id") %>%
  left_join(MLE.est, by = "tile.id") %>% 
  left_join(DF.est, by = "tile.id")

# non-sf version
final.estimates.df <- final.estimates.sf %>% 
  st_drop_geometry()

# vector with names of the relevant estimates
names.estimates <- final.estimates.sf %>% 
  # dplyr::select(pop, starts_with("u.")) %>% # all estimates
  dplyr::select(pop, matches("VOR|u.SB|DF1$|200")) %>% # only "final" ones for mapping
  st_drop_geometry() %>% 
  names() 

4.2 Evaluation: 1d Density

# calculate density dataset for all estimates and GTP 
cdf.compare <- final.estimates.df %>% 
  dplyr::select(tile.id, all_of(names.estimates)) %>% 
  pivot_longer(cols = -tile.id, names_to = "estimates", values_to = "values") %>% 
  split(.$estimates) %>% 
  map(~custom_ecdf_prep(.)) %>% 
  map(~dplyr::select(., cum.prob.comp, pop.plot)) %>%
  map(~mutate(., cum.prob.comp = round(cum.prob.comp, 3))) %>% # effective plot sample --> faster plotting excluding overplot
  map_dfr(~distinct(.), .id = "type")
## Warning in mask$eval_all_mutate(quo): NANs can be present


# ECCDF plot
(ECCDF.pop.plot <- cdf.compare %>% 
    ggplot() + 
    geom_line(aes(x = pop.plot, y = cum.prob.comp,
                  color = type), size = 1) + 
    scale_color_ptol() +
    scale_y_log10(labels = scales::trans_format("log10", 
                                                scales::math_format(10^.x)),
                  minor_breaks = minor.breaks) +
    scale_x_log10(labels = scales::trans_format("log10", 
                                                scales::math_format(10^.x)),
                  minor_breaks = minor.breaks) +
    annotation_logticks(sides = "lb") +
    labs(title = "ECCDF of estimators and GTP", y = "log10(ECCDF)", x = "log10(Mobile phones)",  
         colour = "") +
    theme(legend.position = "bottom"))
Comparing ECCDF of estimates to the GTP

Figure 4.1: Comparing ECCDF of estimates to the GTP

ggsave("Plots/eccdf.estimates.png", ECCDF.pop.plot, device = "png")
## Saving 7 x 5 in image

4.3 Evaluation: 2d Density

# names of specified estimators for order control
names.order.estimator <- c("u.flat", 
                           "u.VOR.tower", "u.VOR.offset", "u.VOR.barycenter",
                           "u.SB",
                           "u.MLE", 
                           "u.DF")

# define additional necessary rescalings of the area (next to 1x1)
rescale.factor.list <- list(area.1x1 = 1,
                            area.2x2 = 2, 
                            area.4x4 = 4, 
                            area.8x8 = 8)

# aggregate estimate values based on rescaling level
area.rescaled.grid <- map(rescale.factor.list, 
                          ~st_make_grid(area$area.sf, cellsize = area$area.params[["base.tile.size"]] * .x))

# define relevant estimates
mse.relevant.estimators <- final.estimates.sf %>% 
  dplyr::select(tile.id, pop, matches("VOR|SB|flat|1|10|100|200")) %>% 
  dplyr::select(-matches("prior"))

# develop a list element with grid aggregated values
mse.est <- map(area.rescaled.grid, ~aggregate(mse.relevant.estimators, by = .x, FUN = mean, join = st_contains)) %>% 
  map(~st_drop_geometry(.))

# define data frame with the necessary variables for visualization of estimate vs. GTP
point <- mse.est %>% 
  map(~mutate(., tile.id.rescaled = row_number())) %>% 
  map(~dplyr::select(., tile.id.rescaled, pop, starts_with("u."))) %>% 
  map_dfr(~pivot_longer(., cols = -c(tile.id.rescaled, pop), 
                        names_to = "estimator", values_to = "estimate"), .id = "scale") %>% 
  mutate(rescale.factor = as.numeric(str_extract(scale, "[[:digit:]]")),
         iteration = str_extract(estimator, "[[:digit:]]+"),
         kind = str_extract(estimator, "[[:alpha:][:punct:]]+")) %>% 
  mutate(iteration = case_when(is.na(iteration) ~ 0,
                               TRUE ~ as.numeric(iteration))) %>% 
  mutate(estimator.ordered = factor(kind, levels = names.order.estimator))


# selected estimators (iterations) for 2d density plots
scatter.names <- c("u.MLE1", "u.MLE10", "u.MLE100", "u.MLE200",
                   "u.VOR.barycenter", "u.VOR.tower")

# custom 2d density plots
scatter.density.plots <- scatter.names %>% 
  map(~scatter_density(point, estimator.name = .x)) %>%
  set_names(scatter.names)

# print
ggpubr::as_ggplot(scatter.density.plots$u.MLE1)

ggpubr::as_ggplot(scatter.density.plots$u.MLE10)

ggpubr::as_ggplot(scatter.density.plots$u.MLE100)

ggpubr::as_ggplot(scatter.density.plots$u.MLE200)

ggpubr::as_ggplot(scatter.density.plots$u.VOR.barycenter)

ggpubr::as_ggplot(scatter.density.plots$u.VOR.tower)

# save
ggsave("Plots/u.MLE1.2d.density.png", scatter.density.plots$u.MLE1, device = "png")
ggsave("Plots/u.MLE10.2d.density.png", scatter.density.plots$u.MLE10, device = "png")
ggsave("Plots/u.MLE100.2d.density.png", scatter.density.plots$u.MLE100, device = "png")
ggsave("Plots/u.MLE200.2d.density.png", scatter.density.plots$u.MLE200, device = "png")
ggsave("Plots/u.VOR.barycenter.2d.density.png", scatter.density.plots$u.VOR.barycenter, device = "png")
ggsave("Plots/u.VOR.tower.2d.density.png", scatter.density.plots$u.VOR.tower, device = "png")

4.4 Evaluation: Spatial Density

# define legend labels for maps
maps.labels <- list("GTP  ", 
                    "VOR_t", "VOR_o", "VOR_b", 
                    "SB   ", "MLE  ", "DF1  ", "DF200")

# check if there is divergence, what are the maximum estimates per tile for each estimator
max.maps <- final.estimates.sf %>% 
  st_drop_geometry() %>% 
  dplyr::select(tile.id, pop, matches("VOR|u.SB|DF1$|200")) %>% # only specific ones for mapping
  summarise_all(max) %>% 
  pivot_longer(cols = -tile.id, names_to = "estimator", values_to = "estimate")


# Define break points for discretized spatial density plots
breaks <- c(0, 2, 5, 10, 20, 50, 100, 200, 350, Inf)
maps.input <- final.estimates.sf %>% 
  dplyr::select(tile.id, pop, all_of(names.estimates)) %>% 
  mutate(across(c(pop, starts_with("u.")), ~cut(., breaks = breaks, dig.lab = 7, right = F)))

# Build maps and print
(maps.estimation.density <- names.estimates %>%
    map2(., maps.labels, ~map_density(data = maps.input, var = .x, label = .y)) %>%
    set_names(names.estimates))
## $pop

## 
## $u.VOR.tower

## 
## $u.VOR.offset

## 
## $u.VOR.barycenter

## 
## $u.SB

## 
## $u.MLE200

## 
## $u.DF1

## 
## $u.DF200

# save
ggsave("Plots/pop.map.png", maps.estimation.density$pop, device = "png")
ggsave("Plots/u.VOR.tower.map.png", maps.estimation.density$u.VOR.tower, device = "png")
ggsave("Plots/u.VOR.offset.map.png", maps.estimation.density$u.VOR.offset, device = "png")
ggsave("Plots/u.VOR.barycenter.map.png", maps.estimation.density$u.VOR.barycenter, device = "png")
ggsave("Plots/u.SB.map.png", maps.estimation.density$u.SB, device = "png")
ggsave("Plots/u.MLE200.map.png", maps.estimation.density$u.MLE200, device = "png")
ggsave("Plots/u.DF1.map.png", maps.estimation.density$u.DF1, device = "png")
ggsave("Plots/u.DF200.map.png", maps.estimation.density$u.DF200, device = "png")

4.5 Evaluation: KWD

Computing the Kantorovitch Wassterstein Distance (KWD), a.k.a. Earth Movers Distance, is done by using the package of Prof. Stefano Gualandi SpatialKWD. This package is the result of the article that is referenced here, which develops an computationally efficient approximation method of the KWD. The package offers the very convenient function compareOneToMany(), which assumes the main parameters Coordinates, Weights and L. The first describes the point coordinates of each tile, for which we use the centroid of each tile, the second uses the GTP and the estimates, the third parameter specifies the approximation, i.e. the accuracy of the approximated KWD. Here, we use the default (L = 3), which basically means that the approximation error will be in the worst case 1.29%. The output value is used as an upper bound for the KWD estimate and the worst case adjustment acts as a lower bound.

This function is particularly convenient, as it allows to implement the Weights object to be a matrix, i.e. as many estimates can be implemented as the user wants. The first column is always assumed to be the GTP, which also acts as the reference.

# develop dataframe with GTP, all estimates and tile centroids
kwd.helper.est <- final.estimates.sf %>% 
  dplyr::select(-c(elevation, type)) %>%
  mutate(u.flat = mean(pop)) %>% 
  mutate(lon = map_dbl(geometry, ~st_centroid(.x)[[1]]),
         lat = map_dbl(geometry, ~st_centroid(.x)[[2]])) %>% 
  st_drop_geometry()

# Coordinates object
coordinates <- kwd.helper.est %>% 
  dplyr::select(lon, lat) %>% 
  as.matrix()

# Weights object
weights <- kwd.helper.est %>% 
  dplyr::select(pop, matches("u.")) %>% 
  as.matrix()

# Approximation parameter (the higher the more accurate)
L = 3

# Run KWD
kwd.final <- compareOneToMany(coordinates, weights, L = L, recode = TRUE)
## CompareOneToMany, Solution method: APPROX
## WARNING: the Xs input coordinates are not consecutives integers.
## WARNING: the Ys input coordinates are not consecutives integers.
## INFO: Recoding the input coordinates to consecutive integers.
## INFO: change <timelimit> to 14400.000000
## INFO: change <verbosity> to silent
## INFO: change <opt_tolerance> to 0.000001
paste("KWD runtime ( L =", L, "):", round(kwd.final$runtime / 60, 0), "min for", 
      ncol(weights) - 1, "estimates and", 
      length(final.estimates.df$tile.id), "tiles")
## [1] "KWD runtime ( L = 3 ): 17 min for 47 estimates and 160000 tiles"
# Define names for estimators
names.weights <- colnames(weights)[-1]

# Develop data frame on the estimtor level with respective KWD values
kwd.eval <- tibble(estimator = names.weights,
                   kwd = kwd.final$distance * 1) %>%  # rescaling according to scale
  mutate(kwd.lower.bound = kwd - ((kwd / 100) * 1.29)) %>%  # for L=3 within 1 percent 
  mutate(iteration = str_extract(estimator, "[[:digit:]]+"),
         kind = str_extract(estimator, "[[:alpha:][:punct:]]+")) %>%
  mutate(iteration = case_when(is.na(iteration) ~ 0,
                               TRUE ~ as.numeric(iteration))) %>% 
  group_by(kind) %>% 
  mutate(min.kwd.kind = min(kwd),
         kind.group = row_number() / max(row_number())) %>% # find minimum per estimator (for ordering help)
  ungroup() %>% 
  arrange(desc(min.kwd.kind), iteration) %>% 
  mutate(final.order = row_number()) %>% 
  mutate(estimator.new = factor(final.order, labels = estimator))
# define the flat estimator KWD value for reference purposes
# this is only implemented as a caption in the following plot to prevent scale distortiton
flat.ref <- round(as.numeric(kwd.eval[kwd.eval$estimator == "u.flat", "kwd"]), 2)

# develop KWD bar plot for selected estimators
(kwd.final.estimates.plot <- kwd.eval %>% 
    filter(kind.group == 1 | estimator == "u.DF1",
           !str_detect(estimator, "flat|prior")) %>% 
    ggplot(aes(x = estimator.new, y = kwd, fill = kind, alpha = kind.group)) + 
    geom_bar(stat = "identity", position = position_dodge(width = 0.9)) + 
    geom_errorbar(aes(ymin = kwd.lower.bound, ymax = kwd), position = position_dodge(width = 0.9), width = 0.25) +
    geom_text(aes(x = estimator.new, y = kwd, label = round(kwd, 2)), 
              position = position_dodge(0.1), hjust = -0.1, color = "Black", size = 3) +
    scale_alpha(range = c(0.5, 1), 
                guide = F
                # labels = unique(kwd.eval$iteration)
    ) + 
    scale_fill_ptol(guide = FALSE) +
    coord_flip() +
    labs(x = "", y = "KWD", 
         alpha = "Iteration", 
         subtitle = paste0("(Reference: Flat = ", flat.ref, ")")) + 
    theme(legend.position = "bottom"))
KWD (L=3) values of the final estimates

Figure 4.2: KWD (L=3) values of the final estimates

# save
ggsave(filename = "Plots/kwd.final.estimates.plot.png", plot = kwd.final.estimates.plot, device = "png")
## Saving 7 x 5 in image

4.6 Evaluation: KWD convergence

# keep only iterated estimators
kwd.eval.convergence <- kwd.eval %>% 
  filter(!iteration == 0)

# develop line plot
(kwd.convergence.plot.log.text <- kwd.eval.convergence %>% 
    dplyr::select(estimator, kwd, kwd.lower.bound, iteration, kind) %>% 
    ggplot(aes(x = iteration, y = kwd)) +
    geom_line(aes(color = kind)) +
    geom_point(aes(color = kind)) +
    geom_ribbon(aes(ymin = kwd.lower.bound, ymax = kwd, group = kind), alpha = 0.2) +
    geom_text_repel(aes(color = kind, label = round(kwd, 2)), size = 3, show.legend  = F) +
    scale_x_log10(labels = scales::trans_format("log10", scales::math_format(10^.x)),
                  minor_breaks = minor.breaks) +
    annotation_logticks(sides = "b") +
    labs(color = "Estimator",
         x = "Iteration",
         y = "KWD"))
## Warning: ggrepel: 9 unlabeled data points (too many overlaps). Consider
## increasing max.overlaps
Convergence behavior of adjusted and renormlaized DF and MLE Poisson estimator in terms of KWD

Figure 4.3: Convergence behavior of adjusted and renormlaized DF and MLE Poisson estimator in terms of KWD

# save
ggsave("Plots/kwd.convergence.png", kwd.convergence.plot.log.text, device = "png")
## Saving 7 x 5 in image
## Warning: ggrepel: 9 unlabeled data points (too many overlaps). Consider
## increasing max.overlaps
sessionInfo()
## R version 4.0.5 (2021-03-31)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 18.04.5 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1
## 
## locale:
##  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
##  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
##  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
## [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
## 
## attached base packages:
## [1] grid      stats     graphics  grDevices utils     datasets  methods  
## [8] base     
## 
## other attached packages:
##  [1] DT_0.17              knitr_1.31           gridExtra_2.3       
##  [4] ggpointdensity_0.1.0 ggrepel_0.9.1        viridis_0.5.1       
##  [7] viridisLite_0.3.0    ggthemes_4.2.4       SpatialKWD_0.4.0    
## [10] mobloc_0.5-1         Matrix_1.2-18        stars_0.4-3         
## [13] abind_1.4-5          furrr_0.2.2          future_1.21.0       
## [16] raster_3.4-5         sp_1.4-4             sf_0.9-6            
## [19] data.table_1.14.0    forcats_0.5.1        stringr_1.4.0       
## [22] dplyr_1.0.5          purrr_0.3.4          readr_1.4.0         
## [25] tidyr_1.1.3          tibble_3.1.0         ggplot2_3.3.3       
## [28] tidyverse_1.3.0     
## 
## loaded via a namespace (and not attached):
##  [1] minqa_1.2.4         colorspace_2.0-0    ggsignif_0.6.1     
##  [4] ellipsis_0.3.1      class_7.3-18        rio_0.5.26         
##  [7] rgdal_1.5-18        fs_1.5.0            rstudioapi_0.13    
## [10] ggpubr_0.4.0        listenv_0.8.0       farver_2.1.0       
## [13] fansi_0.4.2         lubridate_1.7.10    xml2_1.3.2         
## [16] splines_4.0.5       codetools_0.2-18    doParallel_1.0.16  
## [19] jsonlite_1.7.2      nloptr_1.2.2.2      broom_0.7.6        
## [22] dbplyr_2.0.0        compiler_4.0.5      httr_1.4.2         
## [25] backports_1.2.1     assertthat_0.2.1    cli_2.3.1          
## [28] htmltools_0.5.1.1   tools_4.0.5         gtable_0.3.0       
## [31] glue_1.4.2          Rcpp_1.0.6          carData_3.0-4      
## [34] cellranger_1.1.0    jquerylib_0.1.3     vctrs_0.3.6        
## [37] nlme_3.1-151        iterators_1.0.13    lwgeom_0.2-5       
## [40] xfun_0.22           globals_0.14.0      ps_1.5.0           
## [43] lme4_1.1-26         openxlsx_4.2.3      rvest_0.3.6        
## [46] lifecycle_1.0.0     statmod_1.4.35      rstatix_0.7.0      
## [49] MASS_7.3-53.1       scales_1.1.1        hms_1.0.0          
## [52] parallel_4.0.5      yaml_2.2.1          curl_4.3           
## [55] VCA_1.4.3           sass_0.3.1          stringi_1.5.3      
## [58] highr_0.8           foreach_1.5.1       e1071_1.7-4        
## [61] boot_1.3-27         zip_2.1.1           rlang_0.4.10       
## [64] pkgconfig_2.0.3     evaluate_0.14       lattice_0.20-41    
## [67] htmlwidgets_1.5.3   labeling_0.4.2      cowplot_1.1.1      
## [70] tidyselect_1.1.0    parallelly_1.23.0   magrittr_2.0.1     
## [73] bookdown_0.22       R6_2.5.0            generics_0.1.0     
## [76] DBI_1.1.1           pillar_1.5.1        haven_2.3.1        
## [79] foreign_0.8-81      withr_2.4.1         units_0.6-7        
## [82] modelr_0.1.8        crayon_1.4.1        car_3.0-10         
## [85] KernSmooth_2.23-18  utf8_1.2.1          rmarkdown_2.7      
## [88] readxl_1.3.1        reprex_0.3.0        digest_0.6.27      
## [91] classInt_0.4-3      numDeriv_2016.8-1.1 munsell_0.5.0      
## [94] bslib_0.2.4
LS0tCnRpdGxlOiAiU3VwcGxlbWVudGFyeSBtYXRlcmlhbDogVG95d29ybGQgZ2VuZXJhdGlvbiwgRXN0aW1hdGlvbiBhbmQgRXZhbHVhdGlvbiIKYXV0aG9yOiAiRmFiaW8gUmljY2lhdG8sIEFuZ2VsbyBDb2x1Y2NpYSwgTWFyY28gUmFtbGphayIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgYm9va2Rvd246Omh0bWxfZG9jdW1lbnQyOgogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0aGVtZTogc2FuZHN0b25lCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICBmaWdfY2FwdGlvbjogeWVzCmtuaXQ6IChmdW5jdGlvbihpbnB1dF9maWxlLCBlbmNvZGluZykgeyBvdXRfZGlyIDwtICdkb2NzJzsgcm1hcmtkb3duOjpyZW5kZXIoaW5wdXRfZmlsZSwKICBlbmNvZGluZz1lbmNvZGluZywgb3V0cHV0X2ZpbGU9ZmlsZS5wYXRoKGRpcm5hbWUoaW5wdXRfZmlsZSksIG91dF9kaXIsICdpbmRleC5odG1sJykpfSkKLS0tCgpUaGlzIG5vdGVib29rIHByb3ZpZGVzIHN1cHBsZW1lbnRhcnkgbWF0ZXJpYWwgYXMgd2VsbCBhcyB0aGUgY29tcGxldGUgY29kZSBmb3IgcmVwcm9kdWNpbmcgdGhlIHRveXdvcmxkLCBleGVjdXRpbmcgYWxsIGVzdGltYXRvcnMgYW5kIGV2YWx1YXRpbmcgdGhlc2UgZXN0aW1hdG9ycy4gVGhlIHRveXdvcmxkIGlzIGJhc2VkIG9uIGEgc2VtaS1zeW50aGV0aWMgZGF0YSBnZW5lcmF0ZWQgcHJvY2Vzcy4gRm9yIHRoaXMsIGNlbnN1cyBkYXRhIGZyb20gR2VybWFueSBvbiBhIDEwMG1cKjEwMG0gcmVndWxhciBncmlkIGhhcyBiZWVuIHVzZWQsIHdoaWNoIGNhbiBiZSBkb3dubG9hZGVkIFtoZXJlXShodHRwczovL3d3dy56ZW5zdXMyMDExLmRlL0RFL0hvbWUvQWt0dWVsbGVzL0RlbW9ncmFmaXNjaGVHcnVuZGRhdGVuLmh0bWw/bm49MzA2NTQ3NCkuIEVhY2ggZWxlbWVudCBpbiB0aGlzIGdyaWQgaXMgZXhwcmVzc2VkIGFzIGEgdGlsZS4gRm9yIGNvbXB1dGF0aW9uIHB1cnBvc2VzIG9ubHkgYSBzbWFsbCBhcmVhIG9mIEdlcm1hbnkgd2FzIHVzZWQgZm9yIHRoZSB0b3l3b3JsZCwgbmFtZWx5IHRoZSBhcmVhIG9mIE11bmljaCBhbmQgaXRzIG5lYXIgc3Vycm91bmRpbmdzLiBUaGlzIGZvY3VzIGFyZWEgaW5jbHVkZXMgMTYwLDAwMCB0aWxlcy4gVGhlIGNvZGUgZm9yIGNsaXBwaW5nIHRoaXMgc3BlY2lmaWMgYXJlYSBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9SLXJhbWxqYWsvTU5PX21vYmRlbnNpdHkvYmxvYi9tYXN0ZXIvQ29kZS9NdW5pY2hfZ2VuZXJhdGUuUiksIHdoaWNoIGlzIGFsc28gcGFydCBvZiB0aGlzIHJlc2VhcmNoIHJlcG9zaXRvcnkuIEZvciBhIG1vYmlsZSBwaG9uZSBwb3B1bGF0aW9uIHRoZSByZWd1bGFyIGNlbnN1cyBwb3B1bGF0aW9uIHZhbHVlcyBhcmUgdXNlZC4gVG8gbWltaWMgdGhlIG1vYmlsZSBwaG9uZSBwb3B1bGF0aW9uIG9mIG9uZSBtb2JpbGUgbmV0d29yayBvcGVyYXRvciAoTU5PKSB0aGUgcG9wdWxhdGlvbiBpcyByZWR1Y2VkIHRvIGFib3V0IGEgdGhpcmQuCgpGdXJ0aGVybW9yZSwgY3VzdG9tIGZ1bmN0aW9ucyBoYXZlIGJlZW4gYnVpbHQgYW5kIGFyZSB1c2VkIHRocm91Z2hvdXQgdGhpcyBub3RlYm9vaywgd2hpY2ggY2FuIGJlIGZvdW5kIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vUi1yYW1samFrL01OT19tb2JkZW5zaXR5L2Jsb2IvbWFzdGVyL0NvZGUvcGlwZWxpbmUlMjBmdW5jdGlvbnMuUikuIEluIG5lYXIgdGltZSwgdGhlc2Ugd2lsbCBiZSByZWxlYXNlZCB3aXRoaW4gYW4gUi1wYWNrYWdlLgoKVGhlIG5vdGVib29rIGhhcyB0aHJlZSBtYWluIHBhcnRzOiBUb3l3b3JsZCBHZW5lcmF0aW9uLCBFc3RpbWF0aW9uLCBFdmFsdWF0aW9uLgoKIyBHZW5lcmFsIFNldHVwOiBMb2FkaW5nIFBhY2thZ2VzIGFuZCBDdXN0b20gRnVuY3Rpb25zCgpgYGB7ciBwYWNrYWdlcywgbWVzc2FnZT1GQUxTRX0KIyBEYXRhIG1hbmlwdWxhdGlvbgpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShkYXRhLnRhYmxlKSAKCiMgU3BhdGlhbCBvcGVyYXRpb25zCmxpYnJhcnkoc2YpCmxpYnJhcnkocmFzdGVyKQpsaWJyYXJ5KGZ1cnJyKQpsaWJyYXJ5KHN0YXJzKQoKIyBNYXRyaXggb3BlcmF0aW9ucwpsaWJyYXJ5KE1hdHJpeCkKCiMgTU5PIGRhdGEgaGFuZGxpbmcgYW5kIHByb3BhZ2F0aW9uIG1vZGVsIHNldHVwCiMgQ3JlZGl0cyB0byBQcm9mLiBNYXJ0aWpuIFRlbm5la2VzIGh0dHBzOi8vZ2l0aHViLmNvbS9tdGVubmVrZXMvbW9ibG9jCmxpYnJhcnkobW9ibG9jKQoKIyBDb21wYXJpc29uIG9mIDJkIGhpc3RvZ3JhbXMgKEthbnRvcm92aXRjaCBXYXNzZXJzdGVpbiBkaXN0YW5jZSBhLmsuYS4gRWFydGggTW92ZXJzIGRpc3RhbmNlKQojIENyZWRpdHMgdG8gUHJvZi4gU3RlZmFubyBHdWFsYW5kaSBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvU3BhdGlhbEtXRC9TcGF0aWFsS1dELnBkZgpsaWJyYXJ5KFNwYXRpYWxLV0QpCgojIE91dHB1dCBvcmdhbmlzYXRpb24gYW5kIHBsb3R0aW5nIHN1cHBvcnQKbGlicmFyeShnZ3RoZW1lcykKbGlicmFyeSh2aXJpZGlzKQpsaWJyYXJ5KGdncmVwZWwpCmxpYnJhcnkoZ2dwb2ludGRlbnNpdHkpCmxpYnJhcnkoZ3JpZCkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoRFQpCgojIHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eQpzZXQuc2VlZCg0MikKCgojIExvYWRpbmcgQ3VzdG9tIGZ1bmN0aW9ucwpzb3VyY2UoIkNvZGUvcGlwZWxpbmUgZnVuY3Rpb25zLlIiKQoKYGBgCgojIFRveXdvcmxkIEdlbmVyYXRpb24KClRoaXMgcGFydCBpcyBjb25jZXJuZWQgd2l0aCBnZW5lcmF0aW5nIGEgdG95d29ybGQgdGhhdCBpcyBiYXNlZCBvbiB0aGUgYWJvdmUgc3BlY2lmaWVkIGZvY3VzIGFyZWEuIEFmdGVyIHNwZWNpZnlpbmcgdGhlIHBvcHVsYXRpb24gYXNwZWN0IG9mIHRoZSB0b3l3b3JsZCwgdGhlIHJhZGlvIG5ldHdvcmsgd2lsbCBiZSBzcGVjaWZpZWQuCgojIyBUb3l3b3JsZCBHZW5lcmF0aW9uOiBQb3B1bGF0aW9uCgpUaGlzIHN1YmNoYXB0ZXIgZGVmaW5lcyB0aGUgZ2VuZXJhdGlvbiBvZiBhIHBvcHVsYXRpb24uIEFzIG1lbnRpb25lZCBhYm92ZSB0aGUgZm9jdXMgYXJlYSBpcyB6b29tZWQgaW50byB0aGUgYXJlYSBvZiBNdW5pY2ggYW5kIGl0cyBuZWFyIHN1cnJvdW5kaW5ncy4gVGhlIGZvbGxvd2luZyBjaHVuayBzcGVjaWZpZXMgdGhlIG5lY2Vzc2FyeSBvYmplY3RzIHRvIGNvbnRpbnVlIHdpdGggdGhlIGNyZWF0aW9uIG9mIGEgcmFkaW8gbmV0d29yayBpbiB0aGlzIGZvY3VzIGFyZWEuCgpgYGB7ciBwb3B1bGF0aW9uLWRhdGF9CiMgZGF0YSByZWFkIGluCm11bmljaC5yYXcgPC0gcmVhZFJEUygiRGF0YS9tdW5pY2gucmRzIikKCiMgZGVmaW5lIHJhc3RlciBvYmplY3QgZnJvbSBmb2N1cyBhcmVhCm11bmljaC5yYXN0ZXIgPC0gcmFzdGVyRnJvbVhZWihtdW5pY2gucmF3LCBjcnMgPSBzdF9jcnMoMzAzNSkkcHJvajRzdHJpbmcpCgojIGRlZmluZSBlbXB0eSBsaXN0IG9iamVjdCB3aGVyZSBhbGwgR1RQIG9iamVjdHMgd2lsbCBiZSBzdG9yZWQKbXVuaWNoIDwtIE5VTEwKCiMgZGVmaW5lIHNmIHZlcnNpb24gb2YgcmFzdGVyIG9iamVjdAptdW5pY2gkYXJlYS5zZiA8LSBtdW5pY2gucmFzdGVyICU+JQogIHN0X2FzX3N0YXJzKCkgJT4lCiAgc3RfYXNfc2YoKSAlPiUKICBkcGx5cjo6c2VsZWN0KHRpbGUuaWQsIHBvcCwgZWxldmF0aW9uKSAlPiUKICBtdXRhdGUodHlwZSA9ICJOQSIpICMgb25seSBuZWNlc3NhcnkgaWYgZGlmZmVyZW50IHRpbGUgdHlwZXMgY2FuIGJlIGRlZmluZWQgKHVyYmFuLCBydXJhbCwgZXRjLi4uKQoKIyByZWd1bGFyIGRhdGFmcmFtZSB2ZXJzaW9uCm11bmljaCRhcmVhLmRmIDwtIG11bmljaCRhcmVhLnNmICU+JQogIHN0X2Ryb3BfZ2VvbWV0cnkoKQoKIyB1bmlvbml6ZWQgdmVyc2lvbiBvZiBmb2N1cyBhcmVhCm11bmljaCRhcmVhLnVuaW9uIDwtIG11bmljaCRhcmVhLnNmICU+JQogIHN0X3VuaW9uKCkKCiMgYm91bmRpbmcgYm94IGNvb3JkaW5hdGVzIG9mIGZvY3VzIGFyZWEKbXVuaWNoJGFyZWEuYmJveCA8LSBtdW5pY2gkYXJlYS51bmlvbiAlPiUKICBzdF9iYm94KGNycyA9IHNmOjpzdF9jcnMoMzAzNSkpCgojIHNwZWNpZnkgcmFzdGVyIG9iamVjdCBhbmQgdGlsZSBpZCBudW1iZXIKbXVuaWNoJGFyZWEucmFzdGVyIDwtIG11bmljaC5yYXN0ZXIgJT4lCiAgcmFzdGVyKC4sIGxheWVyID0gInRpbGUuaWQiKQoKIyBzcGVjaWZ5IHJhc3RlciBvYmplY3QgYW5kIGVsZXZhdGlvbiB2YWx1ZSBvZiBlYWNoIHRpbGUgKGhlcmUgY29uc2lkZXJlZCBhcyBjb25zdGFudCkKbXVuaWNoJGFyZWEuZWxldmF0aW9uIDwtIG11bmljaC5yYXN0ZXIgJT4lCiAgcmFzdGVyKC4sIGxheWVyID0gImVsZXZhdGlvbiIpCgojIG51bWJlciBvZiB0aWxlcwptdW5pY2gkYXJlYS5wYXJhbXNbWyJ0aWxlLm51bSJdXSA8LSBsZW5ndGgobXVuaWNoJGFyZWEuZGYkdGlsZS5pZCkKCiMgc2l6ZSBvZiB0aWxlcwptdW5pY2gkYXJlYS5wYXJhbXNbWyJiYXNlLnRpbGUuc2l6ZSJdXSA8LSBhcy5udW1lcmljKHNxcnQoc3RfYXJlYShtdW5pY2gkYXJlYS5zZlsxLF0pKSkKCiMgc3Ryb2luZyBldmVyeXRoaW5nIGluIGFyZWEgb2JqZWN0CmFyZWEgPC0gbXVuaWNoCmBgYAoKYGBge3IgZ3RwLW1hcCwgZmlnLmNhcD0iU3BhdGlhbCBwb3B1bGF0aW9uIGRlbnNpdHkgb2YgdGhlIGdyb3VuZCB0cnV0aCBwb3B1bGF0aW9uIn0KIyBhZGp1c3RhYmxlIGJyZWFrIHBvaW50cyBmb3IgbWFwIGNhdGVnb3JpZXMKYnJlYWtzIDwtIGMoMCwgMiwgNSwgMTAsIDIwLCA1MCwgMTAwLCAyMDAsIDM1MCwgSW5mKQojIHBsb3QgbWFwIGFuZCBwcmludAphcmVhJGFyZWEuc2YgJT4lIAogIG11dGF0ZShwb3AuY2F0ID0gY3V0KHBvcCwgYnJlYWtzID0gYnJlYWtzLCBkaWcubGFiID0gNywgcmlnaHQgPSBGKSkgJT4lIAogIG1hcF9kZW5zaXR5KGRhdGEgPSAuLCB2YXIgPSAicG9wLmNhdCIsIGxhYmVsID0gIkdUUCIpCgpgYGAKCmBgYHtyIHBvcC1zdW1tYXJ5fQojIHN1bW1hcnkgZGVzY3JpcHRpdmVzIG9mIEdUUApwb3Bfc3VtbWFyeV9yZXN1bHRzKGFyZWEkYXJlYS5zZikgJT4lIAogIGRwbHlyOjpzZWxlY3Qobi50aWxlcyA9IG4udHlwZSwgbWVhbi5wb3AsIHNkLnBvcCwgbWluLnBvcCwgbWF4LnBvcCwgc3VtLnBvcCkKCmBgYAoKYGBge3IgZGVuc2l0eS1wbG90LCBmaWcuY2FwPSJEaXN0cmlidXRpb24gb2YgdGhlIGdyb3VuZCB0cnV0aCBwb3B1bGF0aW9uIn0KIyBFQ0NERiBhbmQgRUNERiBvZiBHVFAKKGRlbnNpdHlfcGxvdHMoYXJlYSRhcmVhLmRmKSkKYGBgCgojIyBUb3l3b3JsZCBHZW5lcmF0aW9uOiBSYWRpbyBuZXR3b3JrCgpUaGlzIHN1YmNoYXB0ZXIgcmVmZXJzIHRvIHRoZSBkZXZlbG9wbWVudCBvZiBhIHJhZGlvIG5ldHdvcmsgd2l0aGluIG91ciBmb2N1cyBhcmVhLiBEZXZlbG9waW5nIHRoZSByYWRpbyBuZXR3b3JrIGlzIGhlYXZpbHkgZGVwZW5kZW50IG9uIHRoZSBgbW9ibG9jYCBwYWNrYWdlLCB3aGljaCBpcyBwcm9tb3RlZCB0aHJvdWdoIHRoZSBFdXJvcGVhbiBTdGF0aXN0aWNhbCBTeXN0ZW0uIEhvd2V2ZXIsIGNlcnRhaW4gZnVuY3Rpb25zIGhhdmUgYmVlbiBhZGp1c3RlZCB0byB0aGUgYXV0aG9ycyBuZWVkcy4KCkluIGdlbmVyYWwsIHRoZSBgbW9ibG9jYCBwYWNrYWdlIGFsbG93cyB0byBkZWZpbmUgbWFueSBwYXJhbWV0ZXJzLCBob3dldmVyLCBpZiB0aGV5IGFyZSBub3QgZGVmaW5lZCwgZGVmYXVsdCBwYXJhbWV0ZXJzLCBzZXQgYnkgdGhlIHBhY2thZ2UsIGFyZSB1c2VkLiBUaGlzIG1ha2VzIGl0IHZlcnkgZWFzeSB0byBpbXBsZW1lbnQgYXMgbXVjaCAvIGFzIGxpdHRsZSBpbmZvcm1hdGlvbiBvbmUgaGFzIG9uIGEgY2VydGFpbiBuZXR3b3JrIGFuZCBhbHdheXMgbWFraW5nIGl0IHdvcmsuIFRoZSBmb2xsb3dpbmcgYWRqdXN0YWJsZSBwYXJhbWV0ZXJzIGFuZCBkZWZhdWx0IHZhbHVlcyBhcmUgcHJvdmlkZWQgdGhyb3VnaCB0aGUgcGFja2FnZSBmb3IgY3JlYXRpbmcgYSByYWRpbyBuZXR3b3JrIGFuZCBtb2RlbGxpbmcgdGhlIHNpZ25hbCBzdHJlbmd0aCBpbiBhIGZvY3VzIGFyZWE6CgpgYGB7cn0KIyBwb3NzaWJsZSBtb2Jsb2MgcGFyYW1ldGVycwptb2Jsb2NfcGFyYW0oKQpgYGAKClRoaXMgdG95d29ybGQgY29udGFpbnMgYSByYWRpbyBuZXR3b3JrIHdpdGggdGhyZWUgbGF5ZXJzIChNYWNybywgTWVzbyBhbmQgTWljcm8pLiBUaGUgZGV2ZWxvcG1lbnQgb2YgZWFjaCBsYXllciBzdGFydHMgd2l0aCBhIGhleGFnb25hbCBncmlkIGluIHdoaWNoIHRoZSBwb2ludHMgZGVmaW5lIHRvd2VyIGxvY2F0aW9ucy4gVGhlIGhleGFnb25zIGhhdmUgZGlmZmVyZW50IHNpemVzIChpLmUuIHRvd2VyIGRpc3RhbmNlKSBkZXBlbmRlbnQgb24gdGhlIGxheWVyIGFuZCBjb250YWluIHNvbWUgcmFuZG9tbmVzcyB3aXRoaW4gdGhlIGxheWVyIChqaXR0ZXIpIHRvIHByZXZlbnQgZXN0aW1hdGlvbiBhcnRpZmFjdHMuIEVhY2ggbGF5ZXIgc3BhbnMgb3ZlciB0aGUgY29tcGxldGUgZm9jdXMgYXJlYS4KCk9uIGVhY2ggdG93ZXIgdGhyZWUgZGlyZWN0aW9uYWwgYW50ZW5uYXMgYXJlIHBsYWNlZCB0aGF0IGFyZSBkaXJlY3RlZCBpbiBhIDEyMMKwIGFuZ2xlIHRvIGVhY2ggb3RoZXIuIEFsbCBsYXllcnMgY29udGFpbiBhIHJvdGF0aW9uIHBhcmFtZXRlciB0byBwcmV2ZW50IGFudGVubmFzIG9mIGRpZmZlcmVudCBsYXllcnMgYnJvYWRjYXN0aW5nIGludG8gdGhlIGV4YWN0IHNhbWUgZGlyZWN0aW9uLCBpbiByZWZlcmVuY2UgdG8gdGhlIGZvY3VzIGFyZWEuIE5vIG9tbmlkaXJlY3Rpb25hbCBhbnRlbm5hcyBhcmUgaW1wbGVtZW50ZWQgaW4gdGhpcyB0b3l3b3JsZCwgdGhlcmVmb3JlLCBhbGwgYG1vYmxvY2AgcGFyYW1ldGVycyB3aXRoIHRoZSBzdWZmaXggIlxfc21hbGwiIGFyZSBub3QgdXNlZC4KClRoZSBhbnRlbm5hcyBhcmUgc3BlY2lmaWVkIHdpdGggbGF5ZXIgc3BlY2lmaWMgcGFyYW1ldGVycyAoZS5nLiBoZWlnaHQsIHBvd2VyLCBwYXRoIGxvc3MgZXhwb25lbnQsIGV0Yy4pLiBBbGwgYW50ZW5uYXMgYXJlIHNwZWNpZmllZCBpbiBhIHNvIGNhbGxlZCBjZWxscGxhbiBpbiB3aGljaCBhbGwgcGFyYW1ldGVycyBhcmUgbmVzdGVkL2FzanVzdGVkLiBXaGVuIHRoZSBjZWxscGxhbiBpcyBjb21wbGV0ZWQgdGhlIGFudGVubmEgc3BlY2lmaWMgYnJvYWRjYXN0aW5nIHByb2ZpbGUgKGkuZS4gY2VsbCBwcm9maWxlKSBpcyBlc3RpbWF0ZWQgYW5kIHByb2plY3RlZCBvbnRvIHRoZSBmb2N1cyBhcmVhLiBUaGUgZnVuY3Rpb24gYGNvbXB1dGVfc2lnX3N0cmVuZ3RoKClgIGNvbXB1dGVzIHRoZSBkaXN0YW5jZSwgc2lnbmFsIHN0cmVuZ3RoIGFuZCBzaWduYWwgZG9taW5hbmNlIGJldHdlZW4gYW55IHRpbGUgYW5kIGFueSBhbnRlbm5hLiBGdXJ0aGVybW9yZSwgYSBtaW5pbXVtIHBhcmFtZXRlciBpcyBpbXBsZW1lbnRlZCB0aGF0IGRlZmluZXMgdGhlIG1pbmltdW0gc2lnbmFsIGRvbWluYW5jZSB2YWx1ZSBhbiBhbnRlbm5hLXRpbGUgcmVsYXRpb25zaGlwIG5lZWRzIHRvIGhhdmUgaW4gb3JkZXIgdG8gYmUgY29uc2lkZXJlZCAiY292ZXJlZCIuIEZvciB0aGUgZm9sbG93aW5nIGVzdGltYXRvcnMsIGl0IG5lZWRzIHRvIGJlIGFzc3VyZWQgdGhhdCBldmVyeSB0aWxlIGlzIHN1ZmZpY2llbnRseSBjb3ZlcmVkLCBtZWFuaW5nLCB0aGF0IHRoZXJlIGlzIGF0IGxlYXN0IG9uZSBhbnRlbm5hLXRpbGUgcmVsYXRpb25zaGlwIHRoYXQgaGFzIGEgc2lnbmFsIGRvbWluYW5jZSB2YWx1ZSBoaWdoZXIgdGhhbiB0aGUgbWluaW11bSB0aHJlc2hvbGQuCgpgYGB7ciwgbmV0d29yay1wYXJhbWV0ZXJzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMCwgZmlnLmNhcD0iVGhlb3JldGljYWwgcmFkaW8gbmV0d29yayBwYXJhbWV0ZXJzIGZvciBlYWNoIGxheWVyIn0KIyBzcGVjaWZ5IHBhcmFtZXRlcnMgb2YgZWFjaCBjZWxsCk1BLmNlbGwucGFyYW0ubW9ibG9jIDwtIG1vYmxvY19wYXJhbShXID0gNSwgIyBQb3dlciBpbiBXYXR0cwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmFuZ2UgPSAxMDAwMCwgIyBtYXhpbXVtIGNvdmVyYWdlIHJhbmdlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbGUgPSAzLjQsICMgUGF0aCBsb3NzIGV4cG9uZW50CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQgPSAxMCwgIyBoZWlnaHQgb2YgdGhlIGFudGVubmEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pZHBvaW50ID0gLTg1LCAjIG1pZHBvaW50IHBhcmFtZXRlciBvZiB0aGUgbG9naXN0aWMgZnVuY3Rpb24gZm9yIHNpZ25hbCBkb21pbmFuY2UKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0ZWVwbmVzcyA9IDAuMTUsICMgc3RlZXBuZXNzIHBhcmFtZXRlciBvZiB0aGUgbG9naXN0aWMgZnVuY3Rpb24gZm9yIHNpZ25hbCBkb21pbmFuY2UKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpZ19kX3RoID0gMC4wNSkgIyBkb21pbmFuY2UgbWluaW11bSB0aHJlc2hvbGQgCgpNRS5jZWxsLnBhcmFtLm1vYmxvYyA8LSBtb2Jsb2NfcGFyYW0oVyA9IDUwLCByYW5nZSA9IDM1MDAsIHBsZSA9IDMuOCwgaGVpZ2h0ID0gMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaWRwb2ludCA9IC04NSwgc3RlZXBuZXNzID0gMC4zLCBzaWdfZF90aCA9IDAuMDUpCgpNSS5jZWxsLnBhcmFtLm1vYmxvYyA8LSBtb2Jsb2NfcGFyYW0oVyA9IDEsIHJhbmdlID0gMzUwMCwgcGxlID0gNCwgaGVpZ2h0ID0gNiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pZHBvaW50ID0gLTg1LCBzdGVlcG5lc3MgPSAwLjQsIHNpZ19kX3RoID0gMC4wNSkKCiMgY3JlYXRlIGRhdGFmcmFtZSBmb3IgdGhlb3JldGljYWwgc2lnbmFsIHN0cmVuZ3RoIGRpc3RyaWJ1dGlvbgpwYXJhbS5kZiA8LSB0aWJibGUoY2VsbC5raW5kID0gYygiTUEiLCAiTUUiLCAiTUkiKSwKICAgICAgICAgICAgICAgICAgIGxhYmVsID0gYygiTWFjcm8iLCAiTWVzbyIsICJNaWNybyIpLAogICAgICAgICAgICAgICAgICAgVyA9IGMoTUEuY2VsbC5wYXJhbS5tb2Jsb2MkVywgTUUuY2VsbC5wYXJhbS5tb2Jsb2MkVywgTUkuY2VsbC5wYXJhbS5tb2Jsb2MkVyksCiAgICAgICAgICAgICAgICAgICBwbGUgPSBjKE1BLmNlbGwucGFyYW0ubW9ibG9jJHBsZSwgTUUuY2VsbC5wYXJhbS5tb2Jsb2MkcGxlLCBNSS5jZWxsLnBhcmFtLm1vYmxvYyRwbGUpLAogICAgICAgICAgICAgICAgICAgcmFuZ2UgPSBjKE1BLmNlbGwucGFyYW0ubW9ibG9jJHJhbmdlLCBNRS5jZWxsLnBhcmFtLm1vYmxvYyRyYW5nZSwgTUkuY2VsbC5wYXJhbS5tb2Jsb2MkcmFuZ2UpLAogICAgICAgICAgICAgICAgICAgbWlkcG9pbnQgPSBjKE1BLmNlbGwucGFyYW0ubW9ibG9jJG1pZHBvaW50LCBNRS5jZWxsLnBhcmFtLm1vYmxvYyRtaWRwb2ludCwgTUkuY2VsbC5wYXJhbS5tb2Jsb2MkbWlkcG9pbnQpLAogICAgICAgICAgICAgICAgICAgc3RlZXBuZXNzID0gYyhNQS5jZWxsLnBhcmFtLm1vYmxvYyRzdGVlcG5lc3MsIE1FLmNlbGwucGFyYW0ubW9ibG9jJHN0ZWVwbmVzcywgTUkuY2VsbC5wYXJhbS5tb2Jsb2Mkc3RlZXBuZXNzKSwKICAgICAgICAgICAgICAgICAgIGRvbWluYW5jZS50aCA9IGMoTUEuY2VsbC5wYXJhbS5tb2Jsb2Mkc2lnX2RfdGgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1FLmNlbGwucGFyYW0ubW9ibG9jJHNpZ19kX3RoLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNSS5jZWxsLnBhcmFtLm1vYmxvYyRzaWdfZF90aCkpCgojIHJlZHVjZWQgZGF0YSBmcmFtZSBvZiB0aGVvcmV0aWNhbCBzaWduYWwgc3RyZW5ndGggZGlzdHJpYnV0aW9uCnBhcmFtLmRmLnJlZHVjZWQgPC0gcGFyYW0uZGYgJT4lIAogIGRwbHlyOjpzZWxlY3QoY2VsbC5raW5kLCBkb21pbmFuY2UudGgpCgojIHRoZW9yZXRpY2FsIHNpZ25hbCBzdHJlbmd0aCBwYXJhbWV0ZXIgcGxvdHMKc2lnLnByYW0ucGxvdHMgPC0gc2lnX3BhcmFtX3Bsb3RzKHBhcmFtLmRmID0gcGFyYW0uZGYsIHJhbmdlLm1heCA9IDIwMDAwLCBiYXNlX3NpemUgPSAxMSkKCiMgcHJpbnQKKGEgPC0gZ2dwdWJyOjphc19nZ3Bsb3Qoc2lnLnByYW0ucGxvdHMkZmluYWwpKQojIHNhdmUKZ2dzYXZlKCJQbG90cy9jb3ZlcmFnZS5kaWFnLnBuZyIsIGEsIGRldmljZSA9ICJwbmciLCB3aWR0aCA9IDEwKQoKc2V0LnNlZWQoMTAwKQoKIyBjcmVhdGUgdG93ZXIgcG9zaXRpb25zIHdpdGggYXR0YWNoZWQgY2VsbHMKTUEuY2VsbHMudW5wYXJhbSA8LSBjcmVhdGVfY2VsbHMoYXJlYS5zZiA9IGFyZWEkYXJlYS5zZiwgIyBmb2N1cyBhcmVhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvd2VyLmRpc3QgPSA4NTAwLCAjIHRvd2VyIGRpc3RhbmNlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdGF0aW9uLmRlZyA9IDAsICMgcm90YXRpb24gcGFyYW1ldGVyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGppdHRlciA9IDEwMDAsICMgYW1vdW50IG9mIGppdHRlciBpbiBtZXRlcnMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc21hbGwgPSBGQUxTRSwgIyBkaXJlY3Rpb25hbCBjZWxsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1YnNjcmlwdCA9ICJNQSIsICMgbGF5ZXIgbGFiZWwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDMpCgpNRS5jZWxscy51bnBhcmFtIDwtIGNyZWF0ZV9jZWxscyhhcmVhLnNmID0gYXJlYSRhcmVhLnNmLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3dlci5kaXN0ID0gMzUwMCwgcm90YXRpb24uZGVnID0gMzUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGppdHRlciA9IDcwMCwgc21hbGwgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3Vic2NyaXB0ID0gIk1FIiwgc2VlZCA9IDcpCgpNSS5jZWxscy51bnBhcmFtIDwtIGNyZWF0ZV9jZWxscyhhcmVhLnNmID0gYXJlYSRhcmVhLnNmLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3dlci5kaXN0ID0gMTAwMDAsIHJvdGF0aW9uLmRlZyA9IDYwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBqaXR0ZXIgPSAyMDAwLCBzbWFsbCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWJzY3JpcHQgPSAiTUkiLCBzZWVkID0gMTApCgoKIyBjcmVhdGUgdGhlIGNlbGxwbGFuIGFuZCB2YWxpZGF0ZSBpdCB3aXRoIHRoZSBzcGVjaWZpZWQgcGFyYW1ldGVycwpNQS5jZWxscGxhbi52YWwgPC0gY3JlYXRlX2NlbGxwbGFuKGFyZWEuc2YgPSBhcmVhJGFyZWEuc2YsICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcmVhLmJib3ggPSBhcmVhJGFyZWEuYmJveCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYS5lbGV2YXRpb24gPSBhcmVhJGFyZWEuZWxldmF0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGxzLnVucGFyYW0gPSBNQS5jZWxscy51bnBhcmFtLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwucGFyYW0ubW9ibG9jID0gTUEuY2VsbC5wYXJhbS5tb2Jsb2MpCgpNRS5jZWxscGxhbi52YWwgPC0gY3JlYXRlX2NlbGxwbGFuKGFyZWEuc2YgPSBhcmVhJGFyZWEuc2YsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYS5iYm94ID0gYXJlYSRhcmVhLmJib3gsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYS5lbGV2YXRpb24gPSBhcmVhJGFyZWEuZWxldmF0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGxzLnVucGFyYW0gPSBNRS5jZWxscy51bnBhcmFtLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwucGFyYW0ubW9ibG9jID0gTUUuY2VsbC5wYXJhbS5tb2Jsb2MpCgpNSS5jZWxscGxhbi52YWwgPC0gY3JlYXRlX2NlbGxwbGFuKGFyZWEuc2YgPSBhcmVhJGFyZWEuc2YsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYS5iYm94ID0gYXJlYSRhcmVhLmJib3gsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYS5lbGV2YXRpb24gPSBhcmVhJGFyZWEuZWxldmF0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGxzLnVucGFyYW0gPSBNSS5jZWxscy51bnBhcmFtLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwucGFyYW0ubW9ibG9jID0gTUkuY2VsbC5wYXJhbS5tb2Jsb2MpCiMgY2VsbHBsYW5zIG5lZWQgdG8gYmUgbWFkZSB2YWxpZCEKCmNlbGxwbGFuLmNvbWJpbmVkIDwtIGJpbmRfcm93cyhhc190aWJibGUoTUEuY2VsbHBsYW4udmFsJGNlbGxwbGFuLnZhbCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc190aWJibGUoTUUuY2VsbHBsYW4udmFsJGNlbGxwbGFuLnZhbCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc190aWJibGUoTUkuY2VsbHBsYW4udmFsJGNlbGxwbGFuLnZhbCkpICU+JSAKICBtdXRhdGUoY2VsbC5raW5kID0gc3Vic3RyKGNlbGwsIDEsIDIpKSAlPiUgCiAgbGVmdF9qb2luKHBhcmFtLmRmLnJlZHVjZWQsIGJ5ID0gImNlbGwua2luZCIpICMgam9pbiBkb21pbmFuY2UgdGhyZXNob2xkIHRvIHVzZSBsYXRlciBpbiBjcmVhdGVfc3RyZW5ndGhfbGxoKCkKCiMgdG8gam9pbiB2YXJpYWJsZSBkb21pbmFuY2UudGggbGF0ZXIgb24KY2VsbHBsYW4uY29tYmluZWQucmVkdWNlZCA8LSBjZWxscGxhbi5jb21iaW5lZCAlPiUgCiAgZHBseXI6OnNlbGVjdChjZWxsLCBkb21pbmFuY2UudGgpCgoKIyBjb21wdXRlIHNpZ25hbCBzdHJlbmd0aCBhbmQgZGV2aWNlIHRvIGNlbGwgYXNzb2NpYXRpb24KTUEuc2lnbmFsLnN0cmVuZ3RoIDwtIGNvbXB1dGVfc2lnX3N0cmVuZ3RoKGNwID0gTUEuY2VsbHBsYW4udmFsJGNlbGxwbGFuLnZhbCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYXN0ZXIgPSBhcmVhJGFyZWEucmFzdGVyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtID0gTUEuY2VsbHBsYW4udmFsJGNlbGwucGFyYW0ubW9ibG9jLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsZXZhdGlvbiA9IGFyZWEkYXJlYS5lbGV2YXRpb24pCgpNRS5zaWduYWwuc3RyZW5ndGggPC0gY29tcHV0ZV9zaWdfc3RyZW5ndGgoY3AgPSBNRS5jZWxscGxhbi52YWwkY2VsbHBsYW4udmFsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmFzdGVyID0gYXJlYSRhcmVhLnJhc3RlciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtID0gTUUuY2VsbHBsYW4udmFsJGNlbGwucGFyYW0ubW9ibG9jLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxldmF0aW9uID0gYXJlYSRhcmVhLmVsZXZhdGlvbikKCk1JLnNpZ25hbC5zdHJlbmd0aCA8LSBjb21wdXRlX3NpZ19zdHJlbmd0aChjcCA9IE1JLmNlbGxwbGFuLnZhbCRjZWxscGxhbi52YWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYXN0ZXIgPSBhcmVhJGFyZWEucmFzdGVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyYW0gPSBNSS5jZWxscGxhbi52YWwkY2VsbC5wYXJhbS5tb2Jsb2MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbGV2YXRpb24gPSBhcmVhJGFyZWEuZWxldmF0aW9uKQoKIyBjcmVhdGUgc2lnbmFsIHN0cmVuZ3RoIG9iamVjdCBvZiBhbGwgY2VsbHMKc2lnbmFsLnN0cmVuZ3RoLmNvbWIuZHQgPC0gcmJpbmRsaXN0KGxpc3QoTUEuc2lnbmFsLnN0cmVuZ3RoLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNRS5zaWduYWwuc3RyZW5ndGgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1JLnNpZ25hbC5zdHJlbmd0aCkpCgogIAoKCnNpZ25hbC5zdHJlbmd0aC5zdW1tYXJ5LmhlbHBlciA8LSBzaWduYWwuc3RyZW5ndGguY29tYi5kdCAlPiUKICBhc190aWJibGUoKSAlPiUKICBtdXRhdGUodGlsZS5pZCA9IGFzLmNoYXJhY3RlcihyaWQpKSAlPiUKICBtdXRhdGUoY2VsbC5raW5kID0gc3Vic3RyKGNlbGwsIDEsIDIpKSAlPiUKICBtdXRhdGUoY2VsbC5jaHIgPSBhcy5jaGFyYWN0ZXIoY2VsbCkpICU+JQogIGxlZnRfam9pbihjZWxscGxhbi5jb21iaW5lZC5yZWR1Y2VkLCBieSA9IGMoImNlbGwuY2hyIiA9ICJjZWxsIikpICU+JSAKICBmaWx0ZXIoIXMgPCBkb21pbmFuY2UudGgpICMgZmlsdGVyIHJvd3Mgb3V0IHRoYXQgYXJlIGJlbG93IHRoZSBzZXQgZG9taW5hbmNlIHRocmVzaG9sZAoKc2lnbmFsLnN0cmVuZ3RoLnN1bW1hcnkgPC0gc2lnbmFsLnN0cmVuZ3RoLnN1bW1hcnkuaGVscGVyICU+JSAKICBncm91cF9ieSh0aWxlLmlkKSAlPiUKICBtdXRhdGUobWF4LmRCbSA9IG1heChkQm0pLAogICAgICAgICBtYXgucyA9IG1heChzKSwKICAgICAgICAgbWluLmRpc3QgPSBtaW4oZGlzdCkpICU+JQogIHVuZ3JvdXAoKQoKCiMgaWRlbnRpZnkgdGhlIGNlbGwtdGlsZSByZWxhdGlvbnMgd2l0aCBtYXhpbXVtIHNpZ25hbCBkb21pbmFuY2UgYW5kIGlkZW50aWZ5IHRpbGVzIHRoYXQgYXJlIG5vdCBjb3ZlcmVkIHN1ZmZpY2llbnRseQpzaWduYWwuZG9tIDwtIHNpZ25hbC5zdHJlbmd0aC5zdW1tYXJ5ICU+JSAKICBkaXN0aW5jdCh0aWxlLmlkLCBtYXgucykgJT4lCiAgbGVmdF9qb2luKHNpZ25hbC5zdHJlbmd0aC5zdW1tYXJ5LCBieSA9IGMoInRpbGUuaWQiLCAibWF4LnMiID0gInMiKSkgJT4lIAogIGRwbHlyOjpzZWxlY3QodGlsZS5pZCwgbWF4LnMsIGNlbGwsIGNlbGwua2luZCkgJT4lIAogIG11dGF0ZSh0aWxlLmlkID0gYXMuaW50ZWdlcih0aWxlLmlkKSkgJT4lIAogIGZ1bGxfam9pbihhcmVhJGFyZWEuc2YsIGJ5ID0gInRpbGUuaWQiKSAlPiUgCiAgbXV0YXRlKG1pc3NpbmcgPSBjYXNlX3doZW4oaXMubmEobWF4LnMpIH4gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gMCkpCgojIGhvdyBtYW55IHRpbGVzIGFyZSBub3Qgc3VmZmljaWVudGx5IGNvdmVyZWQKcGFzdGUwKCJOdW1iZXIgb2YgdGlsZXMgd2hpY2ggZG8gbm90IHJlYWNoIHRoZSBzaWduYWwgZG9taW5hbmNlIHRocmVzaG9sZCBvZjogIiAsIHN1bShzaWduYWwuZG9tJG1pc3NpbmcpKQojIHBhc3RlMCgiTnVtYmVyIG9mIHRpbGVzIHdoaWNoIGRvIG5vdCByZWFjaCB0aGUgc2lnbmFsIGRvbWluYW5jZSB0aHJlc2hvbGQgb2YgIiwgc2lnX2RfdGgsICI6ICIgLCBzdW0oc2lnbmFsLmRvbSRtaXNzaW5nKSkKYGBgCgpUaGUgZm9sbG93aW5nIGNodW5rIGlzIG1lcmVseSBleGVjdXRlZCB0byBkZXZlbG9wIGEgc21vb3RoZXIgdmlzdWFsaXphdGlvbiBvZiB0aGUgcmFkaW8gY2VsbCBjb3ZlcmFnZSBwcm9maWxlcy4gRm9yIHRoaXMsIHRoZSBzaWduYWwgc3RyZW5ndGggaXMgY2FsY3VsYXRlZCBvbiBhIGZpbmVyIGdyaWQgMjVtXCoyNW0uIFRoaXMgZmluZXIgZ3JpZCBpcyBvbmx5IHJlbGV2YW50IGZvciB0aGlzIGNodW5rIGFuZCB0aGUgZm9sbG93aW5nIGNvbnRvdXIgdmlzdWFsaXphdGlvbnMuIFRoZSBjb250b3VycyBhcmUgaW50ZXJwb2xhdGVkIGJhc2VkIG9uIHRoZSB0aWxlcycgc2lnbmFsIGRvbWluYW5jZSB2YWx1ZXMgYW5kIHRoZSB0aWxlIGNlbnRyb2lkcy4KCmBgYHtyIGNvbnR1ci1wbG90LXNldHVwLCBtZXNzYWdlPUZBTFNFfQojIHNwZWNpZml5IGhvdyBtdWNoIGZpbmVyIHRoZSBncmlkIHNob3VsZCBiZQpmaW5lci5ncmlkLmZhY3RvciA8LSAyCmZpbmVyLmdyaWQudGlsZS5pZCA8LSBjKDE6NjQwMDAwKSAjIGFkanVzdCB0byBncmlkIGZhY3RvcgoKIyBkaXNhZ2dyZWdhdGUgdGhlIHJlZ3VsYXIgZ3JpZCBiYXNlZCBvbiB0aGUgZmluZXIuZ3JpZC5mYWN0b3IKYXJlYSRhcmVhLnJhc3Rlci5maW5lciA8LSBkaXNhZ2dyZWdhdGUoYXJlYSRhcmVhLnJhc3RlciwgZmluZXIuZ3JpZC5mYWN0b3IpICU+JSAKICBzZXRWYWx1ZXMoLiwgZmluZXIuZ3JpZC50aWxlLmlkKQoKIyBkaXNhZ2dyZWdhdGUgdGhlIGVsZXZhdGlvbiBncmlkIGJhc2VkIG9uIHRoZSBmaW5lci5ncmlkLmZhY3RvcgphcmVhJGFyZWEuZWxldmF0aW9uLmZpbmVyIDwtIGRpc2FnZ3JlZ2F0ZShhcmVhJGFyZWEuZWxldmF0aW9uLCBmaW5lci5ncmlkLmZhY3RvcikKCiMgdHJhbnNmb3JtIHJhc3RlciBiYWNrIHRvIHNmCmFyZWEkYXJlYS5maW5lci5ncmlkLnNmIDwtIHN0X2FzX3NmKHN0X2FzX3N0YXJzKGFyZWEkYXJlYS5yYXN0ZXIuZmluZXIpKQoKIyBjb21wdXRlIHNpZ25hbCBzdHJlbmd0aCBhbmQgZGV2aWNlIHRvIGNlbGwgYXNzb2NpYXRpb24gZm9yIGZpbmVyIGdyaWQgZm9yIGVhY2ggbGF5ZXIKTUEuc2lnbmFsLnN0cmVuZ3RoLmZpbmVyIDwtIGNvbXB1dGVfc2lnX3N0cmVuZ3RoKGNwID0gTUEuY2VsbHBsYW4udmFsJGNlbGxwbGFuLnZhbCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYXN0ZXIgPSBhcmVhJGFyZWEucmFzdGVyLmZpbmVyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtID0gTUEuY2VsbHBsYW4udmFsJGNlbGwucGFyYW0ubW9ibG9jLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsZXZhdGlvbiA9IGFyZWEkYXJlYS5lbGV2YXRpb24uZmluZXIpCgpNRS5zaWduYWwuc3RyZW5ndGguZmluZXIgPC0gY29tcHV0ZV9zaWdfc3RyZW5ndGgoY3AgPSBNRS5jZWxscGxhbi52YWwkY2VsbHBsYW4udmFsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmFzdGVyID0gYXJlYSRhcmVhLnJhc3Rlci5maW5lciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtID0gTUUuY2VsbHBsYW4udmFsJGNlbGwucGFyYW0ubW9ibG9jLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxldmF0aW9uID0gYXJlYSRhcmVhLmVsZXZhdGlvbi5maW5lcikKCk1JLnNpZ25hbC5zdHJlbmd0aC5maW5lciA8LSBjb21wdXRlX3NpZ19zdHJlbmd0aChjcCA9IE1JLmNlbGxwbGFuLnZhbCRjZWxscGxhbi52YWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYXN0ZXIgPSBhcmVhJGFyZWEucmFzdGVyLmZpbmVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyYW0gPSBNSS5jZWxscGxhbi52YWwkY2VsbC5wYXJhbS5tb2Jsb2MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbGV2YXRpb24gPSBhcmVhJGFyZWEuZWxldmF0aW9uLmZpbmVyKQoKc2lnbmFsLnN0cmVuZ3RoLmNvbWIuZHQuZmluZXIuZ3JpZCA8LSByYmluZGxpc3QobGlzdChNQS5zaWduYWwuc3RyZW5ndGguZmluZXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTUUuc2lnbmFsLnN0cmVuZ3RoLmZpbmVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1JLnNpZ25hbC5zdHJlbmd0aC5maW5lcikpCgoKIyBjYWxjdWxhdGUgdGlsZSBjZW50cm9pZCBmb3IgZWFjaCB0aWxlIG9uIHRoZSBmaW5lciBncmlkCmFyZWEucmVkdWNlZC5jb250b3VyIDwtIGFyZWEkYXJlYS5maW5lci5ncmlkLnNmICU+JSAKICBkcGx5cjo6c2VsZWN0KHRpbGUuaWQubnVtID0gdGlsZS5pZCkgJT4lIAogIG11dGF0ZShsb24gPSBtYXBfZGJsKGdlb21ldHJ5LCB+c3RfY2VudHJvaWQoLngpW1sxXV0pLAogICAgICAgICBsYXQgPSBtYXBfZGJsKGdlb21ldHJ5LCB+c3RfY2VudHJvaWQoLngpW1syXV0pKQoKIyBzZXQgdGhlIGNvb3JkaW5hdGUgcmVmZXJlbmNlIHN5c3RlbQpjcnMuc2V0IDwtIHN0X2NycyhhcmVhLnJlZHVjZWQuY29udG91cikKCiMgY29tcHV0ZSB0aGUgYW50ZW5uYSBzcGVjZmljIGJyb2FkY2FzdGluZyBwcm9maWxlIGNvbnRvdXIKIyBkZWZpbmUgdHdvIGNhdGVnb3JpZXM6IGNvbnRvdXIgb2Ygc2lnbmFsIGRvbWluYW5jZSB2YWx1ZXMgYmVsb3cgb3IgZXF1YWwgdG8gMC41IGFuZCBhYm92ZSAwLjUgcmVzdWx0aW5nIGxhdGVyIGluIHR3byBjb250b3VyIGxpbmVzIHBlciBhbnRlbm5hCmNvbnRvdXIuZGF0YS5yYXcgPC0gc2lnbmFsLnN0cmVuZ3RoLmNvbWIuZHQuZmluZXIuZ3JpZCAlPiUKICBhc190aWJibGUoKSAlPiUKICBtdXRhdGUodGlsZS5pZC5udW0gPSBhcy5udW1lcmljKHJpZCkpICU+JQogIG11dGF0ZShjZWxsLmNociA9IGFzLmNoYXJhY3RlcihjZWxsKSkgJT4lCiAgbGVmdF9qb2luKGNlbGxwbGFuLmNvbWJpbmVkLCBieSA9IGMoImNlbGwuY2hyIiA9ICJjZWxsIikpICU+JSAKICBmaWx0ZXIoIXMgPCBkb21pbmFuY2UudGgpICU+JSAKICBtdXRhdGUocy5kaXNjcmV0ZSA9IGNhc2Vfd2hlbihzIDw9IDAuNSB+IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcyA+IDAuNSB+IDIpKSAlPiUgIyBkZWZpbmUgY2F0ZWdvcmlzdGF0aW9uIG9mIGNvbnRvdXJzCiAgZHBseXI6OnNlbGVjdChjZWxsLCBjZWxsLmtpbmQsIHgudG93ID0geCwgeS50b3cgPSB5LCBkaXJlY3Rpb24sIHRpbGUuaWQubnVtLCBzLCBzLmRpc2NyZXRlKSAlPiUgCiAgbGVmdF9qb2luKGFyZWEucmVkdWNlZC5jb250b3VyLCBieSA9IGMoInRpbGUuaWQubnVtIikpICU+JSAKICBzdF9hc19zZihjb29yZHMgPSBjKCJsb24iLCAibGF0IiksIGNycyA9IGNycy5zZXQpIAoKIyBzcGxpdCBhY2NvcmRpbmcgdG8gbGF5ZXIKY29udG91ci5kYXRhLnJhdy5zcGxpdCA8LSBjb250b3VyLmRhdGEucmF3ICU+JSAKICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIAogIHNwbGl0KC4kY2VsbC5raW5kKSAlPiUgCiAgbWFwKH5kaXN0aW5jdCguLCBjZWxsLCB4LnRvdywgeS50b3cpKQoKIyBzdW1tYXJpc2UgYW50ZW5uYSBzcGVjaWZpYyBjb250b3VyIGFzIHBvbHlnb24gYW5kIHRoZW4gY29tcHV0ZSB0aGUgY29udmV4IGh1bGwgdG8gcmVzdWx0IGluIHNpbXBsZSBjb250b3VyIGxpbmUKY29udG91ci5kYXRhLnN1bW1hcnkgPC0gY29udG91ci5kYXRhLnJhdyAlPiUgCiAgZHBseXI6OnNlbGVjdChjZWxsLCBjZWxsLmtpbmQsIHMuZGlzY3JldGUsIHMpICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoY2VsbCwgY2VsbC5raW5kLCBzLmRpc2NyZXRlKSAlPiUKICBkcGx5cjo6c3VtbWFyaXNlKCkgJT4lCiAgc3RfY29udmV4X2h1bGwoKQpgYGAKCmBgYHtyIGNvbnRvdXItTUEsIGZpZy5jYXA9IkNvdmVyYWdlIENvbnRvdXIgb2YgTWFjcm8gY2VsbHMsIHNob3dpbmcgdGhlIGFyZWEgd2l0aCBsZXNzIG9yIGVxdWFsIHRoYW4gMC41IHNpZ25hbCBkb21pbmFuY2UifQooY29udG91ci5jb21wbGV0ZS5NQSA8LSBjb250b3VyLmRhdGEuc3VtbWFyeSAlPiUgCiAgZmlsdGVyKGNlbGwua2luZCA9PSAiTUEiICYgcy5kaXNjcmV0ZSA9PSAiMSIpICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gYXJlYSRhcmVhLnVuaW9uKSArCiAgZ2VvbV9zZihjb2xvciA9ICJyZWQiLCBmaWxsID0gInRyYW5zcGFyZW50IikgKwogIGdlb21fcG9pbnQoZGF0YSA9IGNvbnRvdXIuZGF0YS5yYXcuc3BsaXQkTUEsIGFlcyh4ID0geC50b3csIHkgPSB5LnRvdyksIGNvbG9yID0gImJsYWNrIiwgc2hhcGUgPSAyKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHggPSAiIiwgeSA9ICIiKSkKZ2dzYXZlKCJQbG90cy9jb250b3VyLmNvbXBsZXRlLk1BLmZpbmVyLnBuZyIsIGNvbnRvdXIuY29tcGxldGUuTUEsIGRldmljZSA9ICJwbmciKQpgYGAKCmBgYHtyIGNvbnRvdXItTUUsIGZpZy5jYXA9IkNvdmVyYWdlIENvbnRvdXIgb2YgTWVzbyBjZWxscywgc2hvd2luZyB0aGUgYXJlYSB3aXRoIGxlc3Mgb3IgZXF1YWwgdGhhbiAwLjUgc2lnbmFsIGRvbWluYW5jZSJ9Cihjb250b3VyLmNvbXBsZXRlLk1FIDwtIGNvbnRvdXIuZGF0YS5zdW1tYXJ5ICU+JSAKICBmaWx0ZXIoY2VsbC5raW5kID09ICJNRSIgJiBzLmRpc2NyZXRlID09ICIxIikgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBhcmVhJGFyZWEudW5pb24pICsKICBnZW9tX3NmKGNvbG9yID0gInJlZCIsIGZpbGwgPSAidHJhbnNwYXJlbnQiKSArCiAgZ2VvbV9wb2ludChkYXRhID0gY29udG91ci5kYXRhLnJhdy5zcGxpdCRNRSwgYWVzKHggPSB4LnRvdywgeSA9IHkudG93KSwgY29sb3IgPSAiYmx1ZSIsIHNoYXBlID0gMikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicyh4ID0gIiIsIHkgPSAiIikpCmdnc2F2ZSgiUGxvdHMvY29udG91ci5jb21wbGV0ZS5NRS5maW5lci5wbmciLCBjb250b3VyLmNvbXBsZXRlLk1FLCBkZXZpY2UgPSAicG5nIikKCmBgYAoKYGBge3IgY29udG91ci1NSSwgZmlnLmNhcD0iQ292ZXJhZ2UgQ29udG91ciBvZiBNaWNybyBjZWxscywgc2hvd2luZyB0aGUgYXJlYSB3aXRoIGxlc3Mgb3IgZXF1YWwgdGhhbiAwLjUgc2lnbmFsIGRvbWluYW5jZSJ9Cihjb250b3VyLmNvbXBsZXRlLk1JIDwtIGNvbnRvdXIuZGF0YS5zdW1tYXJ5ICU+JSAKICBmaWx0ZXIoY2VsbC5raW5kID09ICJNSSIgJiBzLmRpc2NyZXRlID09ICIxIikgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBhcmVhJGFyZWEudW5pb24pICsKICBnZW9tX3NmKGNvbG9yID0gInJlZCIsIGZpbGwgPSAidHJhbnNwYXJlbnQiKSArCiAgZ2VvbV9wb2ludChkYXRhID0gY29udG91ci5kYXRhLnJhdy5zcGxpdCRNSSwgYWVzKHggPSB4LnRvdywgeSA9IHkudG93KSwgY29sb3IgPSAiZ3JlZW4iLCBzaGFwZSA9IDIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIGxhYnMoeCA9ICIiLCB5ID0gIiIpKQpnZ3NhdmUoIlBsb3RzL2NvbnRvdXIuY29tcGxldGUuTUkuZmluZXIucG5nIiwgY29udG91ci5jb21wbGV0ZS5NSSwgZGV2aWNlID0gInBuZyIpCmBgYAoKYGBge3IgY29udG91ci1jZXJ0YWluLCBmaWcuY2FwPSJDb3ZlcmFnZSBDb250b3VyIG9mIGEgc2FtcGxlIGNlbGwgb2YgZWFjaCBsYXllciwgc2hvd2luZyB0aGUgZGlzY3JldGl6ZWQgc2lnbmFsIGRvbWluYW5jZSAoY2VsbCBwcm9maWxlKSJ9CgooY29udG91ci5jZXJ0YWluIDwtIGNvbnRvdXIuZGF0YS5zdW1tYXJ5ICU+JSAKICBmaWx0ZXIoc3RyX2RldGVjdChjZWxsLCAiTUEuMTIufE1FLjEwMC58TUkuMTguIikpICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gYXJlYSRhcmVhLnVuaW9uKSArCiAgZ2VvbV9zZihhZXMoY29sb3IgPSBmYWN0b3Iocy5kaXNjcmV0ZSkpLCBmaWxsID0gInRyYW5zcGFyZW50IikgKwogIGdlb21fcG9pbnQoZGF0YSA9IGNvbnRvdXIuZGF0YS5yYXcuc3BsaXQkTUEsIGFlcyh4ID0geC50b3csIHkgPSB5LnRvdyksIGNvbG9yID0gImJsYWNrIiwgc2hhcGUgPSAyKSArCiAgZ2VvbV9wb2ludChkYXRhID0gY29udG91ci5kYXRhLnJhdy5zcGxpdCRNRSwgYWVzKHggPSB4LnRvdywgeSA9IHkudG93KSwgY29sb3IgPSAiYmx1ZSIsIHNoYXBlID0gMikgKwogIGdlb21fcG9pbnQoZGF0YSA9IGNvbnRvdXIuZGF0YS5yYXcuc3BsaXQkTUksIGFlcyh4ID0geC50b3csIHkgPSB5LnRvdyksIGNvbG9yID0gImdyZWVuIiwgc2hhcGUgPSAyKSArCiAgc2NhbGVfY29sb3JfZGlzY3JldGUoIlNpZ25hbCBkb20uIiwgYnJlYWtzID0gYygiMSIsICIyIiksIGxhYmVscyA9IGMoIjAuMDUgPD0gcyA8IDAuNSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAicyA+PSAwLjUiKSkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicyh4ID0gIiIsIHkgPSAiIikpCmdnc2F2ZSgiUGxvdHMvaHVsbC5jZXJ0YWluLmZpbmVyLnBuZyIsIGNvbnRvdXIuY2VydGFpbiwgZGV2aWNlID0gInBuZyIpCgpgYGAKClRoZSBmb2xsb3dpbmcgY2h1bmtzIHZpc3VhbGl6ZSBkaWZmZXJlbnQgdmlld3Mgb24gYW50ZW5uYSBjb3ZlcmFnZSwgc3BlY2lmaWMgZm9yIGVhY2ggbGF5ZXIuCgpgYGB7ciBjb3ZlcmFnZS1raW5kLXNldHVwfQojIG1heGltdW0gb2YgdHdvIHJvd3MgcGVyIHRpbGUsIHRpbGVzIHdpdGggb25seSBvbmUgcm93LCBtZWFuIHRoZXkgYXJlIG9ubHkgY292ZXJlZCBieSBvbmUgY2VsbC5raW5kCnNpZ25hbC5zdHJlbmd0aC5zdW1tYXJ5LmNrIDwtIHNpZ25hbC5zdHJlbmd0aC5zdW1tYXJ5LmhlbHBlciAlPiUgCiAgZ3JvdXBfYnkodGlsZS5pZCwgY2VsbC5raW5kKSAlPiUKICBzdW1tYXJpc2UoY2VsbC5jb3VudCA9IG4oKSwKICAgICAgICAgICAgbWF4LmRCbSA9IG1heChkQm0pLAogICAgICAgICAgICBtYXgucyA9IG1heChzKSwKICAgICAgICAgICAgbWluLmRpc3QgPSBtaW4oZGlzdCkpICU+JSAKICBtdXRhdGUoY2VsbC5jb3VudC5jb21wbGV0ZSA9IHN1bShjZWxsLmNvdW50KSwKICAgICAgICAgbWF4LmRCbS5jb21wbGV0ZSA9IG1heChtYXguZEJtKSwKICAgICAgICAgbWF4LnMuY29tcGxldGUgPSBtYXgobWF4LnMpLCAKICAgICAgICAgbWluLmRpc3QuY29tcGxldGUgPSBtYXgobWluLmRpc3QpKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IC1jKHRpbGUuaWQsIGNlbGwua2luZCksCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gImtpbmQiLCAKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlcyIpICMgcG9zc2libHkgZGlmZmVyZW50IHBpdm90IHRvIGdldCAia2luZCIgaW50byBtdWx0aXBsZSB2YXJpYWJsZXMKYGBgCgpgYGB7ciBoaXN0b2dyYW0tY2VsbHMsIGZpZy5jYXA9IlRoZSBudW1iZXIgb2YgY2VsbHMgYSB0aWxlIGlzIGNvdmVyZWQgYnksIGluIHRvdGFsIGFuZCBwZXIgY2VsbCBsYXllciJ9CiMgaGlzdG9ncmFtIGNlbGxzCih0aWxlLmNvdmVyYWdlLmhpc3QgPC0gc2lnbmFsLnN0cmVuZ3RoLnN1bW1hcnkuY2sgJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3Qoa2luZCwgcGF0dGVybiA9ICJjb3VudCIpKSAlPiUgCiAgbXV0YXRlKGNhdCA9IGNhc2Vfd2hlbihzdHJfZGV0ZWN0KGtpbmQsIHBhdHRlcm4gPSAiY29tcGxldGUiKSB+ICJjb21wbGV0ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gcGFzdGUwKGNlbGwua2luZCkpKSAlPiUgCiAgZGlzdGluY3QodGlsZS5pZCwgY2F0LCAua2VlcF9hbGwgPSBUKSAlPiUgCiAgZHBseXI6OnNlbGVjdCgtY2VsbC5raW5kKSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh2YWx1ZXMpLCBiaW53aWR0aCA9IDEpICsKICAjIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTIsIDEpKSArICMgcmVzcGVjaWZ5IHRvIGJlIGR5bmFtaWMKICBmYWNldF9ncmlkKH5jYXQpICsKICBsYWJzKHRpdGxlID0gIk51bWJlciBvZiBjZWxscyBhIHRpbGUgaXMgY292ZXJlZCBieSIsCiAgICAgICB5ID0gIlRpbGUgY291bnQiLAogICAgICAgeCA9ICJOdW1iZXIgb2YgY2VsbHMiKSkKYGBgCgpgYGB7ciBjb3ZlcmFnZS1tYXAtZGZzfQpjb3ZlcmFnZS5tYXAuZG9tLmRmLk1BIDwtIHNpZ25hbC5zdHJlbmd0aC5zdW1tYXJ5LmNrICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KGtpbmQsIHBhdHRlcm4gPSAibWF4LnMiKSkgJT4lIAogIG11dGF0ZShjYXQgPSBjYXNlX3doZW4oc3RyX2RldGVjdChraW5kLCBwYXR0ZXJuID0gImNvbXBsZXRlIikgfiAiY29tcGxldGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHBhc3RlMChjZWxsLmtpbmQpKSkgJT4lIAogIGZpbHRlcihjYXQgPT0gIk1BIikgJT4lIAogIGRwbHlyOjpzZWxlY3QoLWNlbGwua2luZCkgJT4lIAogIG11dGF0ZSh0aWxlLmlkLm51bSA9IGFzLm51bWVyaWModGlsZS5pZCkpICU+JSAKICBmdWxsX2pvaW4oYXJlYSRhcmVhLnNmLCBieSA9IGMoInRpbGUuaWQubnVtIiA9ICJ0aWxlLmlkIikpICU+JSAgIyBjaGFuZ2UgbGF0ZXIgb24KICBtdXRhdGUobWlzc2luZyA9IGNhc2Vfd2hlbihpcy5uYSh2YWx1ZXMpIH4gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gMCkpICU+JSAKICBzdF9hc19zZigpCgpjb3ZlcmFnZS5tYXAuZG9tLmRmLk1FIDwtIHNpZ25hbC5zdHJlbmd0aC5zdW1tYXJ5LmNrICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KGtpbmQsIHBhdHRlcm4gPSAibWF4LnMiKSkgJT4lIAogIG11dGF0ZShjYXQgPSBjYXNlX3doZW4oc3RyX2RldGVjdChraW5kLCBwYXR0ZXJuID0gImNvbXBsZXRlIikgfiAiY29tcGxldGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHBhc3RlMChjZWxsLmtpbmQpKSkgJT4lIAogIGZpbHRlcihjYXQgPT0gIk1FIikgJT4lIAogIGRwbHlyOjpzZWxlY3QoLWNlbGwua2luZCkgJT4lIAogIG11dGF0ZSh0aWxlLmlkLm51bSA9IGFzLm51bWVyaWModGlsZS5pZCkpICU+JSAKICBmdWxsX2pvaW4oYXJlYSRhcmVhLnNmLCBieSA9IGMoInRpbGUuaWQubnVtIiA9ICJ0aWxlLmlkIikpICU+JSAgIyBjaGFuZ2UgbGF0ZXIgb24KICBtdXRhdGUobWlzc2luZyA9IGNhc2Vfd2hlbihpcy5uYSh2YWx1ZXMpIH4gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gMCkpICU+JSAKICBzdF9hc19zZigpCgpjb3ZlcmFnZS5tYXAuZG9tLmRmLk1JIDwtIHNpZ25hbC5zdHJlbmd0aC5zdW1tYXJ5LmNrICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KGtpbmQsIHBhdHRlcm4gPSAibWF4LnMiKSkgJT4lIAogIG11dGF0ZShjYXQgPSBjYXNlX3doZW4oc3RyX2RldGVjdChraW5kLCBwYXR0ZXJuID0gImNvbXBsZXRlIikgfiAiY29tcGxldGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHBhc3RlMChjZWxsLmtpbmQpKSkgJT4lIAogIGZpbHRlcihjYXQgPT0gIk1JIikgJT4lIAogIGRwbHlyOjpzZWxlY3QoLWNlbGwua2luZCkgJT4lIAogIG11dGF0ZSh0aWxlLmlkLm51bSA9IGFzLm51bWVyaWModGlsZS5pZCkpICU+JSAKICBmdWxsX2pvaW4oYXJlYSRhcmVhLnNmLCBieSA9IGMoInRpbGUuaWQubnVtIiA9ICJ0aWxlLmlkIikpICU+JSAgIyBjaGFuZ2UgbGF0ZXIgb24KICBtdXRhdGUobWlzc2luZyA9IGNhc2Vfd2hlbihpcy5uYSh2YWx1ZXMpIH4gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gMCkpICU+JSAKICBzdF9hc19zZigpCgpjb3ZlcmFnZS5tYXAuZG9tLmRmLmNvbXBsZXRlIDwtIHNpZ25hbC5zdHJlbmd0aC5zdW1tYXJ5LmNrICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KGtpbmQsIHBhdHRlcm4gPSAibWF4LnMiKSkgJT4lIAogIG11dGF0ZShjYXQgPSBjYXNlX3doZW4oc3RyX2RldGVjdChraW5kLCBwYXR0ZXJuID0gImNvbXBsZXRlIikgfiAiY29tcGxldGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHBhc3RlMChjZWxsLmtpbmQpKSkgJT4lIAogIGZpbHRlcihjYXQgPT0gImNvbXBsZXRlIikgJT4lIAogIGRpc3RpbmN0KHRpbGUuaWQsIGNhdCwgLmtlZXBfYWxsID0gVCkgJT4lIAogIGRwbHlyOjpzZWxlY3QoLWNlbGwua2luZCkgJT4lIAogIG11dGF0ZSh0aWxlLmlkLm51bSA9IGFzLm51bWVyaWModGlsZS5pZCkpICU+JSAKICBmdWxsX2pvaW4oYXJlYSRhcmVhLnNmLCBieSA9IGMoInRpbGUuaWQubnVtIiA9ICJ0aWxlLmlkIikpICU+JSAgIyBjaGFuZ2UgbGF0ZXIgb24KICBtdXRhdGUobWlzc2luZyA9IGNhc2Vfd2hlbihpcy5uYSh2YWx1ZXMpIH4gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gMCkpICU+JSAKICBzdF9hc19zZigpCmBgYAoKYGBge3IgY292ZXJhZ2UtbWFwLU1BLCBmaWcuY2FwPSJBY3R1YWwgY292ZXJhZ2UgaGVhdG1hcCBmb3IgTWFjcm8gY2VsbHMifQooY292ZXJhZ2UubWFwLk1BIDwtIGNvdmVyYWdlLm1hcC5kb20uZGYuTUEgJT4lIAogICAgZ2dwbG90KCkgKwogICAgZ2VvbV9zZihhZXMoZmlsbCA9IHZhbHVlcyksIGNvbG9yID0gInRyYW5zcGFyZW50IikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gYXNfdGliYmxlKE1BLmNlbGxwbGFuLnZhbCRjZWxscGxhbi52YWwpLCBhZXMoeCwgeSksCiAgICAgICAgICAgICAgIHNoYXBlID0gMiwgY29sb3IgPSAiI0Y4NzY2RCIpICsKICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gIndoaXRlIiwgaGlnaCA9ICJibGFjayIsIG5hLnZhbHVlID0gInJlZCIsIAogICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDEpKSArCiAgICBsYWJzKHRpdGxlID0gIk1BIENvdmVyYWdlIiwgCiAgICAgICAgIGZpbGwgPSAiU2lnbmFsIERvbWluYW5jZSIsCiAgICAgICAgIHggPSAiIiwKICAgICAgICAgeSA9ICIiKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMCwgMCwgMCwgMCksICJjbSIpLAogICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikpCmBgYAoKYGBge3IgY292ZXJhZ2UtbWFwLU1FLCBmaWcuY2FwPSJBY3R1YWwgY292ZXJhZ2UgaGVhdG1hcCBmb3IgTWVzbyBjZWxscyJ9Cihjb3ZlcmFnZS5tYXAuTUUgPC0gY292ZXJhZ2UubWFwLmRvbS5kZi5NRSAlPiUgCiAgICBnZ3Bsb3QoKSArCiAgICBnZW9tX3NmKGFlcyhmaWxsID0gdmFsdWVzKSwgY29sb3IgPSAidHJhbnNwYXJlbnQiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBhc190aWJibGUoTUUuY2VsbHBsYW4udmFsJGNlbGxwbGFuLnZhbCksIGFlcyh4LCB5KSwKICAgICAgICAgICAgICAgc2hhcGUgPSAyLCBjb2xvciA9ICIjMDBCRkM0IikgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAid2hpdGUiLCBoaWdoID0gImJsYWNrIiwgbmEudmFsdWUgPSAicmVkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgMSkpICsKICAgIGxhYnModGl0bGUgPSAiIE1FIENvdmVyYWdlIiwgCiAgICAgICAgIGZpbGwgPSAiU2lnbmFsIERvbWluYW5jZSIsCiAgICAgICAgIHggPSAiIiwKICAgICAgICAgeSA9ICIiKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMCwgMCwgMCwgMCksICJjbSIpLAogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSkpCgoKYGBgCgpgYGB7ciBjb3ZlcmFnZS1tYXAtTUksIGZpZy5jYXA9IkFjdHVhbCBjb3ZlcmFnZSBoZWF0bWFwIGZvciBNaWNybyBjZWxscyJ9Cihjb3ZlcmFnZS5tYXAuTUkgPC0gY292ZXJhZ2UubWFwLmRvbS5kZi5NSSAlPiUgCiAgICBnZ3Bsb3QoKSArCiAgICBnZW9tX3NmKGFlcyhmaWxsID0gdmFsdWVzKSwgY29sb3IgPSAidHJhbnNwYXJlbnQiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBhc190aWJibGUoTUkuY2VsbHBsYW4udmFsJGNlbGxwbGFuLnZhbCksIGFlcyh4LCB5KSwKICAgICAgICAgICAgICAgc2hhcGUgPSAyLCBjb2xvciA9ICIjMDBCRkM0IikgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAid2hpdGUiLCBoaWdoID0gImJsYWNrIiwgbmEudmFsdWUgPSAicmVkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgMSkpICsKICAgIGxhYnModGl0bGUgPSAiIE1JIENvdmVyYWdlIiwgCiAgICAgICAgIGZpbGwgPSAiU2lnbmFsIERvbWluYW5jZSIsCiAgICAgICAgIHggPSAiIiwKICAgICAgICAgeSA9ICIiKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMCwgMCwgMCwgMCksICJjbSIpLAogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSkpCmBgYAoKYGBge3IgY292ZXJhZ2UtbWFwLWNvbXBsZXRlLCBmaWcuY2FwPSJBY3R1YWwgY292ZXJhZ2UgaGVhdG1hcCBmb3IgYWxsIGNlbGxzIn0KCihjb3ZlcmFnZS5tYXAuY29tcGxldGUgPC0gY292ZXJhZ2UubWFwLmRvbS5kZi5jb21wbGV0ZSAlPiUgCiAgICBnZ3Bsb3QoKSArCiAgICBnZW9tX3NmKGFlcyhmaWxsID0gdmFsdWVzKSwgY29sb3IgPSAidHJhbnNwYXJlbnQiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBjZWxscGxhbi5jb21iaW5lZCwgYWVzKHgsIHksIGNvbG9yID0gY2VsbC5raW5kKSwKICAgICAgICAgICAgICAgc2hhcGUgPSAyKSArCiAgICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJ3aGl0ZSIsIGhpZ2ggPSAiYmxhY2siLCBuYS52YWx1ZSA9ICJyZWQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCAxKSkgKwogICAgbGFicyh0aXRsZSA9ICJDb21wbGV0ZSBjb3ZlcmFnZSIsIAogICAgICAgICBjb2xvciA9ICJUb3dlciBraW5kIiwKICAgICAgICAgZmlsbCA9ICJTaWduYWwgRG9taW5hbmNlIiwKICAgICAgICAgeCA9ICIiLAogICAgICAgICB5ID0gIiIpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICB0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpKSkKYGBgCgpgYGB7ciBjb3ZlcmFnZS1tdWx0aW5vbWlhbC1zZXR1cH0KY292ZXJhZ2UubWFwLmRvbS5kZi5iaW5hcnkgPC0gc2lnbmFsLnN0cmVuZ3RoLnN1bW1hcnkuY2sgJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3Qoa2luZCwgcGF0dGVybiA9ICJtYXgucyIpLAogICAgICAgICAhc3RyX2RldGVjdChraW5kLCBwYXR0ZXJuID0gImNvbXBsZXRlIikpICU+JQogIGdyb3VwX2J5KHRpbGUuaWQpICU+JQogICMgbXV0YXRlKHZhbHVlcyA9IHJvdW5kKHZhbHVlcywgMikpICU+JQogIGZpbHRlcih2YWx1ZXMgPT0gbWF4KHZhbHVlcykpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUodGlsZS5pZC5udW0gPSBhcy5udW1lcmljKHRpbGUuaWQpKSAlPiUKICBmdWxsX2pvaW4oYXJlYSRhcmVhLnNmLCBieSA9IGMoInRpbGUuaWQubnVtIiA9ICJ0aWxlLmlkIikpICU+JSAgIyBjaGFuZ2UgbGF0ZXIgb24KICBtdXRhdGUobWlzc2luZyA9IGNhc2Vfd2hlbihpcy5uYSh2YWx1ZXMpIH4gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gMCkpICU+JQogIHN0X2FzX3NmKCkKYGBgCgpgYGB7ciBjb3ZlcmFnZS1tdWx0aW5vbWlhbC1tYXAsIGZpZy5jYXA9IiJ9Cihjb3ZlcmFnZS5tYXAuYmluYXJ5IDwtIGNvdmVyYWdlLm1hcC5kb20uZGYuYmluYXJ5ICU+JQogICAgZ2dwbG90KCkgKwogICAgZ2VvbV9zZihhZXMoZmlsbCA9IGNlbGwua2luZCksIGNvbG9yID0gInRyYW5zcGFyZW50IiwgYWxwaGEgPSAwLjMsIHNob3cgPSBGQUxTRSkgKwogICAgZ2VvbV9wb2ludChkYXRhID0gY2VsbHBsYW4uY29tYmluZWQsIGFlcyh4LCB5LCBjb2xvciA9IGNlbGwua2luZCksCiAgICAgICAgICAgICAgIHNoYXBlID0gMikgKwogICAgbGFicyh0aXRsZSA9ICJNYXhpbXVtIGNvdmVyYWdlIGNhdGVnb3JpemVkIGJ5IGNlbGwgbGF5ZXIiLAogICAgICAgICBjb2xvciA9ICJDZWxsIGtpbmQiLAogICAgICAgICB4ID0gIiIsCiAgICAgICAgIHkgPSAiIikgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCkpKQpgYGAKCiMjIFRveXdvcmxkIEdlbmVyYXRpb246IERldmljZSB0byBjZWxsIGFzc29jaWF0aW9uCgpUaGlzIHN1YmNoYXB0ZXIgcmVmZXJzIHRvIHRoZSBmaW5hbCB0YXNrIGluIHRoZSB0b3l3b3JsZCBnZW5lcmF0aW9uOiB0aGUgZGV2aWNlIHRvIGNlbGwgYXNzb2NpYXRpb24uIEhlcmUgd2UgYnJpbmcgdGhlIHBvcHVsYXRpb24gd2l0aCB0aGUgbmV0d29yayB0b2dldGhlciBhbmQgc3RvY2hhc3RpY2FsbHkgYXNzaWduIHRoZSBtb2JpbGUgcGhvbmUgcG9wdWxhdGlvbiBpbiBlYWNoIHRpbGUgdG8gdGhlIHJlc3BlY3RpdmUgYW50ZW5uYXMuIFRoZSByZXN1bHQgaXMgdGhlIHNvLWNhbGxlZCBjLXZlY3Rvciwgd2hpY2ggZGVzY3JpYmVzIHRoZSBudW1iZXIgb2YgbW9iaWxlIHBob25lcyBhc3NpZ25lZCB0byBlYWNoIGNlbGwuIFRoZSBiYXNpcyBwYXJhbWV0ZXIgZm9yIHRoaXMgYXNzaWdubWVudCBpcyB0aGUgc2lnbmFsIGRvbWluYW5jZSB3aGljaCBpcyBub3JtYWxpemVkIGluIHRoZSBmb3JtIG9mIGEgY29uZGl0aW9uYWwgcHJvYmFiaWxpdHkuIFRoZXNlIGNvbmRpdGlvbmFsIHByb2JhYmlsaXRpZXMgZGVzY3JpYmUgdGhlIGVsZW1lbnRzIG9mIHRoZSBQLm1hdHJpeCwgd2hpY2ggaXMgdGhlIG1haW4gbW9kZWwgdG8gZXN0aW1hdGUgKGdlb2xvY2F0aW9uIG1vZHVsZSkuCgpgYGB7ciBkZXYtdG8tY2VsbH0KIyBzcGVjaWZ5IHRoZSBvcHRpb24gb2YgZGlmZmVyaW5nIHBhcmFtZXRlcnMgZm9yIHNpZ19kX3RoIGFuZCBtYXhfb3ZlcmxhcHBpbmdfY2VsbHMgZGVwZW5kaW5nIG9uIHRoZSBjZWxsIHR5cGUgaW4gY3VzdG9tIGNyZWF0ZV9zdHJlbmd0aF9sbGggZnVuY3Rpb24KCiMgV29ya2Fyb3VuZDogc2VjdXJpbmcgdGhhdCBzaWdfZF90aCBhbmQgbWF4X292ZXJsYXBwaW5nX2NlbGxzIGFyZSB0aGUgc2FtZSBmb3IgZWFjaCBsYXllcgpzaWduYWwuc3RyZW5ndGgubGxoLnBhcmFtIDwtIGxpc3Qoc2lnX2RfdGggPSBtYXgoY2VsbHBsYW4uY29tYmluZWQucmVkdWNlZCRkb21pbmFuY2UudGgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X292ZXJsYXBwaW5nX2NlbGxzID0gMTAwKQoKIyBkZWZpbmluZyB0aGUgY29ubmVjdGlvbiBwcm9iYWJpbGl0eQpzaWduYWwuc3RyZW5ndGgubGxoLmNvbWJpbmVkIDwtIGNyZWF0ZV9zdHJlbmd0aF9sbGgoc3RyZW5ndGggPSBzaWduYWwuc3RyZW5ndGguY29tYi5kdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbSA9IHNpZ25hbC5zdHJlbmd0aC5sbGgucGFyYW0pICU+JSAKICBhc190aWJibGUoKSAlPiUgCiAgbXV0YXRlKHRpbGUuaWQgPSByaWQpICU+JSAKICBncm91cF9ieSh0aWxlLmlkKSAlPiUKICBtdXRhdGUocGlqID0gc21hcnRfcm91bmQocGFnLCAzKSkgJT4lICMgcm91bmQgdmFsdWVzIHRvIHRoZSB0aGlyZCBkZWNpbWFsIGFuZCBhc3N1cmluZyB0aGF0IGFsbCBjb2x1bW5zICh0aWxlcykgYWRkIHVwIHRvIDEgKGNvbHVtbiBzdG9jYWhzdGljaXR5KQogIHVuZ3JvdXAoKSAlPiUKICBsZWZ0X2pvaW4oYXJlYSRhcmVhLmRmLCBieSA9ICJ0aWxlLmlkIikgJT4lIAogIG11dGF0ZShjb3ZlcmFnZS5raW5kID0gY2FzZV93aGVuKHBvcCA9PSAwIH4gIjAgcG9wdWxhdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGlqID09IDEgfiAiY292ZXJlZCBjb21wbGV0ZWx5IGJ5IG9uZSBhbnRlbm5hIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwaWogPiAwICYgcGlqIDwgMSB+ICJjb3ZlcmVkIGJ5IG11bHRpcGxlIGFudGVubmFzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwaWogPT0gMCB+ICJ0aWxlIGNvdmVyZWQgdW5zdWZmaWNpZW50bHkiKSkgJT4lIAogIGRwbHlyOjpzZWxlY3QoLXBhZykKCiMgYWdncmVnYXRpbmcgYW5kIHNwZWNpZnlpbmcgdGhlIHRpbGVzIHRoYXQgYXJlIHVuY292ZXJlZCAoaWYgdGhlcmUgYXJlIHNvbWUpCnRpbGVzLmNhdCA8LSBzaWduYWwuc3RyZW5ndGgubGxoLmNvbWJpbmVkICU+JSAKICBmaWx0ZXIoIXBpaiA9PSAwKSAlPiUgCiAgZHBseXI6OnNlbGVjdCh0aWxlLmlkLCBjb3ZlcmFnZS5raW5kKSAlPiUgCiAgZ3JvdXBfYnkodGlsZS5pZCkgJT4lIAogIHN1bW1hcmlzZShjb3VudCA9IG4oKSkKCiMgaG93IG1hbnkgdGlsZXMgYXJlIG5vdCBzdWZmaWNpZW50bHkgY292ZXJlZAptaXNzaW5ncyA8LSBhbnRpX2pvaW4oYXJlYSRhcmVhLmRmLCB0aWxlcy5jYXQsIGJ5ID0gInRpbGUuaWQiKSAjIGltcGxlbWVudCBub24gemVybyBwb3AKcGFzdGUoIk51bWJlciBvZiB0aWxlcyB3aGljaCBhcmUgdW5zdWZmaWNpZW50bHkgY292ZXJlZDoiLCBsZW5ndGgobWlzc2luZ3MkdGlsZS5pZCkpCgojIGNvdmVyZWQgb25seSBieSBvbmUgdGlsZQpDLnZlYy5maXhlZC5oZWxwZXIgPC0gc2lnbmFsLnN0cmVuZ3RoLmxsaC5jb21iaW5lZCAlPiUgCiAgZmlsdGVyKGNvdmVyYWdlLmtpbmQgPT0gImNvdmVyZWQgY29tcGxldGVseSBieSBvbmUgYW50ZW5uYSIpICU+JQogIGRwbHlyOjpzZWxlY3QodGlsZS5pZCwgY2VsbCwgcG9wKQoKIyBPbmUgb2JqZWN0IHdoZXJlIHRpbGVzIGFyZSBjb3ZlcmVkIGJ5IG11bHRpcGxlIGNlbGxzCkMudmVjLm11bHRpcGxlLmhlbHBlciA8LSBzaWduYWwuc3RyZW5ndGgubGxoLmNvbWJpbmVkICU+JSAKICBmaWx0ZXIoY292ZXJhZ2Uua2luZCA9PSAiY292ZXJlZCBieSBtdWx0aXBsZSBhbnRlbm5hcyIpICU+JSAKICBncm91cF9zcGxpdCh0aWxlLmlkKSAKCiMgQ2FsY3VsYXRlIHRoZSBudW1iZXIgb2YgY29yZXMgZm9yIHBhcmFsbGVsaXplZCBwcm9jZXNzCm5vX2NvcmVzIDwtIG1pbihhdmFpbGFibGVDb3JlcygpIC0gMSw0KQpwbGFuKG11bHRpc2Vzc2lvbiwgd29ya2VycyA9IG5vX2NvcmVzKQoKIyBTYW1wbGluZyBtb2JpbGUgcGhvbmVzIHdpdGhpbiB0aWxlcyB0byBjZWxscyBkZXBlbmRpbmcgb24gY29ubmVjdGlvbiBwcm9iYWJpbGl0eQojIHBhcmFsbGVsaXplZCBwcm9jZXNzCkMudmVjLm11bHRpcGxlIDwtIEMudmVjLm11bHRpcGxlLmhlbHBlciAlPiUgCiAgZnV0dXJlX21hcCh+c2FtcGxlKHggPSAuJGNlbGwsIG1lYW4oLiRwb3ApLAogICAgICAgICAgICAgICAgICAgICByZXBsYWNlID0gVCwgcHJvYiA9IC4kcGlqKSwgCiAgICAgICAgICAgICAub3B0aW9ucyA9IGZ1cnJyX29wdGlvbnMoc2VlZCA9IFQpLCAucHJvZ3Jlc3MgPSBUKSAlPiUgCiAgZnV0dXJlX21hcChhc190aWJibGUsIC5pZCA9ICJ0aWxlLmlkIiwgLnByb2dyZXNzID0gVCkgJT4lIAogIGZ1dHVyZV9tYXAofmdyb3VwX2J5KC4sIHZhbHVlKSwgLnByb2dyZXNzID0gVCkgJT4lIAogIGZ1dHVyZV9tYXAofnN1bW1hcmlzZSguLCBwb3AuY291bnQucmFuZCA9IG4oKSwgLmdyb3VwcyA9ICJkcm9wIiksIAogICAgICAgICAgICAgLnByb2dyZXNzID0gVCkKCiMgcHVsbGluZyBhbGwgYy12ZWMgaGVscGVyIG9iamVjdHMgdG9nZXRoZXIgYW5kIGRldmVsb3AgZmluYWwgYy12ZWMgZGF0YWZyYW1lCkMudmVjLmRmIDwtIEMudmVjLm11bHRpcGxlICU+JSAKICBiaW5kX3Jvd3MoKSAlPiUgCiAgZHBseXI6OnNlbGVjdChjZWxsID0gdmFsdWUsIHBvcCA9IHBvcC5jb3VudC5yYW5kKSAlPiUgCiAgYmluZF9yb3dzKEMudmVjLmZpeGVkLmhlbHBlcikgJT4lIAogIGdyb3VwX2J5KGNlbGwpICU+JSAKICBzdW1tYXJpc2UocGhvbmVzLnN1bSA9IHN1bShwb3ApKQpgYGAKCmBgYHtyIGMtdmVjLWRpc3RyaWJ1dGlvbiwgZmlnLmNhcD0iRGlzdHJpYnV0aW9uIG9mIHRoZSB0b3l3b3JsZCBwb3B1bGF0aW9uIn0KIyBsb2cgc2NhbGUgYnJlYWtzIGZvciBiYWNrZ3JvdW5kIGdyaWQKbWlub3IuYnJlYWtzIDwtIHJlcCgxOjksIDIxKSAqICgxMF5yZXAoLTEwOjEwLCBlYWNoID0gOSkpCgojIGRldmVsb3AgYy12ZWMgZGF0YXNldCBmb3IgcGxvdHRpbmcgZWNjZGYgYW5kIGVjZGYKYy52ZWMuZGVuc2l0eS5kYXRhIDwtIEMudmVjLmRmICU+JSAKICAgIG11dGF0ZShjLnBsb3QgPSBwaG9uZXMuc3VtICsgMSkgJT4lICAKICAgIGFycmFuZ2UoYy5wbG90KSAlPiUgIAogICAgbXV0YXRlKHByb2IgPSAxIC8gbigpKSAlPiUgIAogICAgbXV0YXRlKGN1bS5wcm9iID0gY3Vtc3VtKHByb2IpKSAlPiUgIAogICAgbXV0YXRlKGN1bS5wcm9iLmNvbXAgPSAxIC0gY3VtLnByb2IpICU+JSAgCiAgICBkcGx5cjo6c2VsZWN0KGN1bS5wcm9iLmNvbXAsIGMucGxvdCkgCgojIEVDQ0RGIHBsb3QgYy12ZWMKYy52ZWMuZWNjZGYgPC0gYy52ZWMuZGVuc2l0eS5kYXRhICU+JSAKICAgIGdncGxvdCgpICsKICAgIGdlb21fcG9pbnQoYWVzKHggPSBjLnBsb3QsIHkgPSBjdW0ucHJvYi5jb21wKSkgKwogICAgc2NhbGVfeV9sb2cxMChsYWJlbHMgPSBzY2FsZXM6OnRyYW5zX2Zvcm1hdCgibG9nMTAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVzOjptYXRoX2Zvcm1hdCgxMF4ueCkpLAogICAgICAgICAgICAgICAgICBtaW5vcl9icmVha3MgPSBtaW5vci5icmVha3MpICsKICAgIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gc2NhbGVzOjp0cmFuc19mb3JtYXQoImxvZzEwIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlczo6bWF0aF9mb3JtYXQoMTBeLngpKSwKICAgICAgICAgICAgICAgICAgbWlub3JfYnJlYWtzID0gbWlub3IuYnJlYWtzKSArCiAgICBhbm5vdGF0aW9uX2xvZ3RpY2tzKHNpZGVzID0gImxiIikgKwogICAgbGFicyh0aXRsZSA9ICJOdW1iZXIgb2YgcGhvbmVzIGEgY2VsbCBoYXMgcmVnaXN0ZXJlZCAoRUNDREYgYW5kIEVDREYpIiwKICAgICAgICAgeSA9ICJsb2coRUNDREYpIiwKICAgICAgICAgeCA9ICJOdW1iZXIgb2YgcGhvbmVzIikKCiMgRUNERiBwbG90IGMtdmVjCmMudmVjLmVjZGYgPC0gYy52ZWMuZGVuc2l0eS5kYXRhICU+JSAgIAogIGdncGxvdCgpICsgCiAgZ2VvbV9wb2ludChhZXMoeCA9IGMucGxvdCwgeSA9IGN1bS5wcm9iLmNvbXApKSArIAogIGxhYnModGl0bGUgPSAiIiwgeSA9ICIiLCB4ID0gIiIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICAgICAgcGxvdC5tYXJnaW4gPSB1bml0KGMoLTAuNSwgMCwgMCwgLTAuNSksICJjbSIpKSAKCgooY29tYmluZWQuYy52ZWMgPC0gYy52ZWMuZWNjZGYgKwogIGFubm90YXRpb25fY3VzdG9tKGdncGxvdEdyb2IoYy52ZWMuZWNkZiksIAogICAgICAgICAgICAgICAgICAgIHhtaW4gPSAwLjIsIHhtYXggPSAzLCAKICAgICAgICAgICAgICAgICAgICB5bWluID0gLTIuOCwgeW1heCA9IC0xKSkKZ2dzYXZlKCJQbG90cy9oaXN0b2dyYW0uYy52ZWMucG5nIiwgY29tYmluZWQuYy52ZWMsIGRldmljZSA9ICJwbmciKQpgYGAKCkluIHRoZSBmb2xsb3dpbmcgY29kZSBjaHVuayB3ZSBhcmUgc3BlY2lmeWluZyB0aGUgUCBtYXRyaXgsIHdpdGggdGlsZXMgYXMgY29sdW1ucywgYW50ZW5uYXMgYXMgcm93cyBhbmQgYW50ZW5uYS10aWxlIHJlbGF0aW9uc2hpcHMgYXMgdGhlIGVsZW1lbnRzLiBUaGlzIG1hdHJpeCBjYW4gYmUgZGVzY3JpYmVkIGFzIHRoZSBjb25uZWN0aW9uIHByb2JhYmlsaXR5LiBFYWNoIGNvbHVtbiBhZGRzIHVwIHRvIDEsIG1lYW5pbmcgaXQgaXMgY29sdW1uIHN0b2NoYXN0aWMuIFRoaXMgcGFydGljdWxhciB2ZXJzaW9uIG9mIHRoZSBjb25uZWN0aW9uIHByb2JhYmlsaXR5IGNhbiBiZSBkZXNjcmliZWQgYXMgZXhhY3QsIGFzIGl0IHJlcHJlc2VudHMgdGhlIHNhbWUgaW5mb3JtYXRpb24gd2hpY2ggaXMgdXNlZCBmb3IgdGhlIGRhdGEgZ2VuZXJhdGluZyBtZWNoYW5pc20gaW4gdGhlIGRldmVsb3BtZW50IG9mIHRoZSB0b3l3b3JsZC4gV2Ugd2lsbCBkZWZpbmUgYSBjb3B5IG9mIHRoZSBQIG1hdHJpeCwgd2hpY2ggd2lsbCBiZSBjYWxsZWQgUFwqIChQLnN0YXIpIGFuZCBkZXNjcmliZXMgdGhlIG1vZGVsbGluZyBwYXJhbWV0ZXIgbmVjZXNzYXJ5IGZvciBhbGwgZXN0aW1hdG9ycywgZXhjbHVkaW5nIHRoZSBWb3Jvbm9pIGVzdGltYXRvcnMuCgpGdXJ0aGVybW9yZSwgaW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rIHdlIGltcGxlbWVudCB0aGUgcHJvY2VzcyBvZiBjb25zb2xpZGF0aW9uIGEuay5hLiB0aGUgZGV2ZWxvcG1lbnQgb2Ygc3VwZXIgdGlsZXMuIFRoZXNlIGFyZSB0aWxlcyBpbiB0aGUgUCBtYXRyaXggdGhhdCBhcmUgaW5kaXN0aW5ndWlzaGFibGUgZnJvbSBlYWNoIG90aGVyIGJhc2VkIG9uIHRoZWlyIHJlbGF0aW9uc2hpcHMgdG8gYW50ZW5uYXMgKGkuZS4gY29sdW1ucyBpbiB0aGUgUCBtYXRyaXggdGhhdCBhcmUgZXhhY3RseSB0aGUgc2FtZSkuIFRoZXNlIHRpbGVzIHdpbGwgeWllbGQgaW4gYWxsIGVzdGltYXRvcnMgdGhlIGV4YWN0IHNhbWUgdmFsdWUsIHRoZXJlZm9yZSwgdG8gcmVkdWNlIGNvbXB1dGF0aW9uYWwgcmVzb3VyY2VzLCB0aGV5IGNhbiBiZSBjb25zb2xpZGF0ZWQgdG8gc3VwZXJ0aWxlcyBhbmQgYWZ0ZXJ3YXJkcyBkaXNhZ2dyZWdhdGVkIGFnYWluLgoKYGBge3IgUC1tYXRyaXh9CiMgZGV2ZWxvcCBsb25nIGZvcm1hdCBvZiBQIG1hdHJpeCB3aGljaCBhbHNvIGNvbnRhaW5zIGNlcnRhaW4gY2VsbHBsYW4gcGFyYW1ldGVycyBhbmQgInplcm8gZWxlbWVudHMiCiMgZGV2ZWxvcCBkaWZmZXJlbnQgdmVyc2lvbnMgb2YgaWQgdmFyaWFibGVzICh0aWxlIGFuZCBhbnRlbm5hcykgZm9yIGVhc2llciBqb2luaW5nClAubG9uZy5jb21wbGV0ZS5kZiA8LSBmdWxsX2pvaW4oc2lnbmFsLnN0cmVuZ3RoLmxsaC5jb21iaW5lZCwgQy52ZWMuZGYsIGJ5ID0gImNlbGwiKSAlPiUgCiAgIyBkcGx5cjo6c2VsZWN0KHRpbGUuaWQsIHBvcCwgZWxldmF0aW9uLCBjZWxsLCB0eXBlLCBkaXN0LCBwaWosIHBob25lcy5zdW0pICU+JSAKICBkcGx5cjo6c2VsZWN0KHRpbGUuaWQsIHBvcCwgY2VsbCwgdHlwZSwgZGlzdCwgcGlqLCBwaG9uZXMuc3VtKSAlPiUgCiAgbXV0YXRlKHRpbGUuaWQuY2hyID0gdGlsZS5pZCkgJT4lIAogIG11dGF0ZSh0aWxlLmlkID0gZmFjdG9yKHRpbGUuaWQpKSAlPiUgCiAgbXV0YXRlKHRpbGUuaWQubnVtID0gYXMubnVtZXJpYyh0aWxlLmlkKSkgJT4lIAogIG11dGF0ZShjZWxsLm51bSA9IGFzLm51bWVyaWMoY2VsbCkpICU+JSAKICBtdXRhdGUoY2VsbC5jaHIgPSBhcy5jaGFyYWN0ZXIoY2VsbCkpCgojIExvbmcgZm9ybWF0IG9mIFAgbWF0cml4IHdpdGggbWluaW1hbCB2YXJpYWJsZXMgYW5kIGFuZCB1bmlxdWUgcm93cwpQLmxvbmcuZGYgPC0gUC5sb25nLmNvbXBsZXRlLmRmICU+JSAKICBkcGx5cjo6c2VsZWN0KHRpbGUuaWQsIHRpbGUuaWQubnVtLCB0aWxlLmlkLmNociwgY2VsbCwgY2VsbC5udW0sIGNlbGwuY2hyLCBwaWopICU+JSAKICBkaXN0aW5jdCgpCgojIFNwYXJzZSBtYXRyaXggdmVyc2lvbiBvZiBQIG1hdHJpeApQLm1hdCA8LSBzcGFyc2VNYXRyaXgoaSA9IFAubG9uZy5kZiRjZWxsLm51bSwgCiAgICAgICAgICAgICAgICAgICAgICBqID0gUC5sb25nLmRmJHRpbGUuaWQubnVtLCAKICAgICAgICAgICAgICAgICAgICAgIHggPSBQLmxvbmcuZGYkcGlqKQoKCiMgV29ya2Zsb3cgZm9yIGNvbnNvbGlkYXRpbmcgcmVndWxhciB0aWxlcyB0byBzdXBlcnRpbGVzIGJhc2VkIG9uIFAgbWF0cml4ClAuc3Rhci5zdXBlcnRpbGUuaGVscGVyIDwtIFAubG9uZy5kZiAlPiUgCiAgZHBseXI6OnNlbGVjdCh0aWxlLmlkLmNociwgY2VsbC5jaHIsIHBpaikgJT4lIAogIGZpbHRlcighcGlqID09IDApICU+JSAjIGFzc3VyZSB0aGF0IG5vICJub24temVybyIgZWxlbWVudHMgYXJlIHByZXNlbnQKICBncm91cF9ieSh0aWxlLmlkLmNocikgJT4lIAogIGFycmFuZ2UoY2VsbC5jaHIpICU+JSAKICBtdXRhdGUoY2VsbC5jb21wID0gcGFzdGUwKGNlbGwuY2hyLCBjb2xsYXBzZSA9ICIiKSwKICAgICAgICAgcGlqLmNvbXAgPSBwYXN0ZTAocGlqLCBjb2xsYXBzZSA9ICIiKSkgJT4lICMgZGV2ZWxvcCB2YXJpYWJsZXMgdGhhdCBjb250YWluIGNoYXJhY3RlciB2ZXJzaW9uIG9mIGFsbCBjZWxsIG5hbWVzIGFuZCBhbGwgY29ubmVjdGlvbiBwcm9iYWJpbGl0aWVzIChvcmRlciBtYXR0ZXJzISEhKQogIHVuZ3JvdXAoKSAlPiUgCiAgZ3JvdXBfYnkoY2VsbC5jb21wLCBwaWouY29tcCkgJT4lICMgZ3JvdXAgYnkgdGhlc2UgdHdvIHZhcmlhYmxlcyB0byBmaW5kIHRpbGUgdGhhdCBhcmUgaWRlbnRpY2FsIGluIHRlcm1zIG9mIHRoZSBQIG1hdHJpeAogIG11dGF0ZShzdXBlcnRpbGUuaWQgPSBjdXJfZ3JvdXBfaWQoKSkgJT4lICMgZGVmaW5lIHRoZSBuZXcgc3VwZXJ0aWxlIGlkCiAgdW5ncm91cCgpICU+JSAKICBtdXRhdGUoc3VwZXJ0aWxlLmlkID0gZmFjdG9yKHN1cGVydGlsZS5pZCkpICU+JSAKICBtdXRhdGUoc3VwZXJ0aWxlLmlkLm51bSA9IGFzLm51bWVyaWMoc3VwZXJ0aWxlLmlkKSkgJT4lIAogIG11dGF0ZShzdXBlcnRpbGUuaWQuY2hyID0gYXMuY2hhcmFjdGVyKHN1cGVydGlsZS5pZCkpICU+JSAKICByaWdodF9qb2luKFAubG9uZy5kZiwgYnkgPSBjKCJ0aWxlLmlkLmNociIsICJjZWxsLmNociIpKSAlPiUgCiAgZHBseXI6OnNlbGVjdCh0aWxlLmlkLCB0aWxlLmlkLm51bSwgdGlsZS5pZC5jaHIsIHN1cGVydGlsZS5pZCwgc3VwZXJ0aWxlLmlkLm51bSwgc3VwZXJ0aWxlLmlkLmNociwgCiAgICAgICAgICAgICAgICBjZWxsLCBjZWxsLm51bSwgY2VsbC5jaHIsIHBpaiA9IHBpai54KSAlPiUgCiAgZmlsdGVyKCFpcy5uYShwaWopKSAKCiMgZGV2ZWxvcCBqb2luZXIgb2JqZWN0IGZvciBrbm93aW5nIHdoaWNoIHJlZ3VsYXIgdGlsZXMgYXJlIHdpdGhpbiBhIHN1cGVydGlsZQpQLnN0YXIuc3VwZXJ0aWxlLmpvaW5lciA8LSBQLnN0YXIuc3VwZXJ0aWxlLmhlbHBlciAlPiUgIAogIGRpc3RpbmN0KHRpbGUuaWQubnVtLCBzdXBlcnRpbGUuaWQsIHN1cGVydGlsZS5pZC5udW0sIHN1cGVydGlsZS5pZC5jaHIpICU+JSAKICBhcnJhbmdlKHRpbGUuaWQubnVtKSAKCiMgZGVmaW5lIFAgbWF0cml4IG9uIHRoZSBzdXBlcnRpbGUgbGV2ZWwKUC5zdGFyLnN1cGVydGlsZS5sb25nLmRmIDwtIFAuc3Rhci5zdXBlcnRpbGUuaGVscGVyICU+JQogIGRpc3RpbmN0KHN1cGVydGlsZS5pZCwgc3VwZXJ0aWxlLmlkLm51bSwgc3VwZXJ0aWxlLmlkLmNociwgY2VsbCwgY2VsbC5udW0sIGNlbGwuY2hyLCBwaWopICU+JSAKICBhcnJhbmdlKGNlbGwubnVtKQoKIyBhcHBlbmQgc3VwZXJ0aWxlIGlkIHRvIHNpbSBhcmVhIGJhc2Ugb2JqZWN0cwphcmVhJGFyZWEuc2YuY29tcGxldGUgPC0gYXJlYSRhcmVhLnNmICU+JSAKICBsZWZ0X2pvaW4oUC5zdGFyLnN1cGVydGlsZS5qb2luZXIsIGJ5ID0gYygidGlsZS5pZCIgPSAidGlsZS5pZC5udW0iKSkKYXJlYSRhcmVhLmRmLmNvbXBsZXRlIDwtIGFyZWEkYXJlYS5kZiAlPiUgCiAgbGVmdF9qb2luKFAuc3Rhci5zdXBlcnRpbGUuam9pbmVyLCBieSA9IGMoInRpbGUuaWQiID0gInRpbGUuaWQubnVtIikpCmBgYAoKIyBFc3RpbWF0aW9uCgpJbiB0aGUgRXN0aW1hdGlvbiBwYXJ0IHdlIGNvbmZpZ3VyZSBhbGwgbmVjZXNzYXJ5IHBhcmFtZXRlcnMgZm9yIHRoZSBkZWZpbmVkIGVzdGltYXRvcnMgaW4gdGhlIG1haW4gcGFwZXIuIEF0IHRoZSBlbmQsIGFsbCBmaW5hbCBlc3RpbWF0ZXMgYXJlIGJvdW5kZWQgdG9nZXRoZXIgaW4gb25lIGRhdGFzZXQgb24gdGhlIHRpbGUgbGV2ZWwuCgojIyBFc3RpbWF0aW9uOiBTZXR1cAoKSGVyZSB3ZSBzcGVjaWZ5IHRoZSBjb25uZWN0aW9uIHByb2JhYmlsaXR5IG1vZGVscy4gSW4gdGhpcyBjYXNlIFAgPSBQXCouIEZ1cnRoZXJtb3JlLCB3ZSBkZWZpbmUgdGhlIGMtdmVjdG9yLCB3aGljaCBjb250YWlucyB0aGUgbnVtYmVyIG9mIG1vYmlsZSBwaG9uZXMgYW4gYW50ZW5uYSBoYXMgY29ubmVjdGVkIHRvIGFuZCB0aGUgcHJpb3IgdmVjdG9yLCB3aGljaCBpcyBhIGNvbnN0YW50IGZvciBlYWNoIHRpbGUgKGhlcmUgMSkuIEZvciB0aGUgZm9sbG93aW5nIGVzdGltYXRvciBmdW5jdGlvbnMgdGhlIHBhcmFtZXRlcnMgYXJlIGFzc3VtZWQgdG8gYmUgaW4gYSBgZGF0YXRhYmxlYCBmb3JtYXQgb3Igc3BhcnNlIE1hdHJpeCB2aWEgYHNwYXJzZU1hdHJpeGAuCgpgYGB7cn0KIyBNb2RlbGxpbmcgdHJ1ZSBQIHdpdGggUCogKFAuc3RhcikKUC5zdGFyLmR0IDwtIFAubG9uZy5kZiAlPiUgCiAgZHBseXI6OnNlbGVjdChpID0gY2VsbC5udW0sIGogPSB0aWxlLmlkLm51bSwgcGlqKSAlPiUgCiAgYXMuZGF0YS50YWJsZSgpCgojIFAqIG9uIHRoZSBzdXBlcnRpbGUgbGV2ZWwgYXMgc3BhcnNlIG1hdHJpeCB2ZXJzaW9uClAuc3Rhci5zdXBlcnRpbGUuc3BtIDwtIHNwYXJzZU1hdHJpeChpID0gUC5zdGFyLnN1cGVydGlsZS5sb25nLmRmJGNlbGwubnVtLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGogPSBQLnN0YXIuc3VwZXJ0aWxlLmxvbmcuZGYkc3VwZXJ0aWxlLmlkLm51bSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPSBQLnN0YXIuc3VwZXJ0aWxlLmxvbmcuZGYkcGlqKQoKIyBQKiBvbiB0aGUgc3VwZXJ0aWxlIGxldmVsIGFzIGRhdGF0YWJsZSB2ZXJzaW9uClAuc3Rhci5zdXBlcnRpbGUuZHQgPC0gZGF0YS50YWJsZShpID0gUC5zdGFyLnN1cGVydGlsZS5sb25nLmRmJGNlbGwubnVtLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaiA9IFAuc3Rhci5zdXBlcnRpbGUubG9uZy5kZiRzdXBlcnRpbGUuaWQubnVtLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGlqID0gUC5zdGFyLnN1cGVydGlsZS5sb25nLmRmJHBpaikKCiMjIyBDIHZlY3RvciwgYWRkaW5nIGFudGVubmFzIHRoYXQgaGF2ZSAwIHBob25lcyB0byBjb21wbGV0ZSB0aGUgdmVjdG9yLCBhcnJhbmdpbmcgaXQgYWNjb3JkaW5nIHRvIHRoZSBhbnRlbm5hLklEIGFuZCBzYXZpbmcgYXMgdmVjdG9yCmMudmVjIDwtIFAubG9uZy5jb21wbGV0ZS5kZiAlPiUgCiAgZGlzdGluY3QoY2VsbCwgY2VsbC5udW0sIGNlbGwuY2hyLCBwaG9uZXMuc3VtKSAlPiUgCiAgcmlnaHRfam9pbihjZWxscGxhbi5jb21iaW5lZCwgYnkgPSAiY2VsbCIpICU+JSAKICBtdXRhdGUocGhvbmVzLnN1bSA9IGNhc2Vfd2hlbihpcy5uYShwaG9uZXMuc3VtKSB+IDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHBob25lcy5zdW0pKSAlPiUgCiAgYXJyYW5nZShjZWxsLm51bSkKCiMgZGVmaW5lIGMtdmVjdG9yIGluIGRhdGF0YWJsZSB2ZXJzaW9uCmMudmVjLmR0IDwtIGRhdGEudGFibGUoaSA9IGMudmVjJGNlbGwubnVtLAogICAgICAgICAgICAgICAgICAgICAgIGMgPSBjLnZlYyRwaG9uZXMuc3VtKQoKCiMjIyBwcmlvciB2ZWN0b3JzCmEudGlsZS5oZWxwZXIgPC0gUC5sb25nLmNvbXBsZXRlLmRmICU+JSAKICBtdXRhdGUoYSA9IDEpICU+JSAKICBkaXN0aW5jdCh0aWxlLmlkLm51bSwgYSkKCiMgZGVmaW5lIGRhdGF0YWJsZSB2ZXJzaW9uIG9mIGNvbnN0YW50IHByaW9yCmEudGlsZS5kdCA8LSBkYXRhLnRhYmxlKGogPSBhLnRpbGUuaGVscGVyJHRpbGUuaWQubnVtLCB1ID0gYS50aWxlLmhlbHBlciRhKQoKIyBkZWZpbmUgY29uc3RhbnQgcHJpb3Igb24gdGhlIHN1cGVydGlsZSBsZXZlbAphLnN1cGVydGlsZS5oZWxwZXIgPC0gUC5zdGFyLnN1cGVydGlsZS5qb2luZXIgJT4lIAogIGdyb3VwX2J5KHN1cGVydGlsZS5pZC5udW0pICU+JSAKICBzdW1tYXJpc2UoYSA9IG4oKSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgZGlzdGluY3Qoc3VwZXJ0aWxlLmlkLm51bSwgYSkgJT4lIAogIGRlZnJhbWUoKQpgYGAKCiMjIEVzdGltYXRpb246IFZvcm9ub2kgKFRvd2VyLCBPZmZzZXQsIEJhcnljZW50ZXIpCgpIZXJlIHdlIGRlZmluZSAzIG9wdGlvbnMgb2YgdGhlIFZvcm9ub2kgZXN0aW1hdG9yLiBBbiBvcHRpb24gYWx3YXlzIGNvcnJlc3BvbmRzIHRvIHRoZSBzZWVkIHRoYXQgaXMgdXNlZCB0byBjb21wdXRlIHRoZSBWb3Jvbm9pIHJlZ2lvbi4gRWFjaCBWb3Jvbm9pIGVzdGltYXRvciBuZWVkcyB0aGUgYy12ZWN0b3IgYW5kIHRoZSBjZWxscGxhbiBhcyBtYWluIHBhcmFtZXRlcnMuCgpUaGUgZmlyc3Qgb3B0aW9uIGlzIGNhbGxlZCAidG93ZXIiLCB3aGljaCBkaXNyZWdhcmRzIGFudGVubmFzIGFuZCBvbmx5IHVzZXMgdG93ZXIgbG9jYXRpb25zIGFzIHNlZWRzLiBUaGVyZWZvcmUsIHRoZSBjLXZlY3RvciBpcyBhZ2dyZWdhdGVkIG9uIHRoZSB0b3dlciBsZXZlbC4KCmBgYHtyIHZvci10b3dlciwgZmlnLmNhcD0iVm9yb25vaSByZWdpb25zIHdpdGggdG93ZXIgbG9jYXRpb25zIGFzIHNlZWRzIn0KIyBWb3Jvbm9pIGVzdGltYXRpb24gd2l0aCB0b3dlciBsb2NhdGlvbnMgYXMgc2VlZHMKVk9SLnRvd2VyIDwtIFZPUl9lc3QoYXJlYSA9IGFyZWEsIAogICAgICAgICAgICAgICAgICAgICBjZWxscGxhbi5jb21iaW5lZCA9IGNlbGxwbGFuLmNvbWJpbmVkLCAKICAgICAgICAgICAgICAgICAgICAgc2lnbmFsLnN0cmVuZ3RoLmNvbWIuZHQgPSBzaWduYWwuc3RyZW5ndGguY29tYi5kdCwgCiAgICAgICAgICAgICAgICAgICAgIEMudmVjLmRmID0gQy52ZWMuZGYsIAogICAgICAgICAgICAgICAgICAgICBzZWVkID0gInRvd2VyIikKVk9SLnRvd2VyLmVzdCA8LSBWT1IudG93ZXIkc2VlZC52b3Jvbm9pLmZpbmFsICU+JSAKICByZW5hbWUodS5WT1IudG93ZXIgPSB1LlZPUikKCiMgcHJpbnQKVk9SLnRvd2VyJFZvcm9ub2kucmVnaW9ucy5wbG90CgojIHNhdmUKZ2dzYXZlKCJQbG90cy9WT1JfcmVnaW9uc190b3dlci5wbmciLCBWT1IudG93ZXIkVm9yb25vaS5yZWdpb25zLnBsb3QsIGRldmljZSA9ICJwbmciKQpgYGAKClRoZSBzZWNvbmQgb3B0aW9uIGlzIGRlZmluZWQgb24gdGhlIGFudGVubmEgbGV2ZWwgYW5kIHVzZXMgdGhlIGxvY2F0aW9uIG9mIGVhY2ggYW50ZW5uYSAod2hpY2ggaXMgdGhlIHNhbWUgZm9yIGVhY2ggYW50ZW5uYSBvZiB0aGUgc2FtZSB0b3dlcikgcGx1cyBhbiBhZGp1c3RhYmxlIG9mZnNldCBpbnRvIHRoZSBicm9hZGNhc3RpbmcgZGlyZWN0aW9uIG9mIHRoZSByZXNwZWN0aXZlIGFudGVubmEgKHdoaWNoIHJlc3VsdHMgaW4gYSBkaWZmZXJlbnQgbG9jYXRpb24gZm9yIGVhY2ggYW50ZW5uYSBvZiB0aGUgc2FtZSB0b3dlcikuIEhlcmUgdGhlIG9mZnNldCBpcyAxMG0uCgpgYGB7ciB2b3Itb2Zmc2V0LCBmaWcuY2FwPSJWb3Jvbm9pIHJlZ2lvbnMgd2l0aCBhbnRlbm5hIG9mZnNldHMgYXMgc2VlZHMifQojIFZvcm9ub2kgZXN0aW1hdGlvbiB3aXRoIGNlbGwgbG9jYXRpb25zICsgb2Zmc2V0IGFzIHNlZWRzClZPUi5vZmZzZXQgPC0gVk9SX2VzdChhcmVhID0gYXJlYSwgCiAgICAgICAgICAgICAgICAgICAgICBjZWxscGxhbi5jb21iaW5lZCA9IGNlbGxwbGFuLmNvbWJpbmVkLCAKICAgICAgICAgICAgICAgICAgICAgIHNpZ25hbC5zdHJlbmd0aC5jb21iLmR0ID0gc2lnbmFsLnN0cmVuZ3RoLmNvbWIuZHQsIAogICAgICAgICAgICAgICAgICAgICAgQy52ZWMuZGYgPSBDLnZlYy5kZiwgCiAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gImNlbGwub2Zmc2V0IiwKICAgICAgICAgICAgICAgICAgICAgIG9mZnNldCA9IDEwKSAKVk9SLm9mZnNldC5lc3QgPC0gVk9SLm9mZnNldCRzZWVkLnZvcm9ub2kuZmluYWwgJT4lIAogIHJlbmFtZSh1LlZPUi5vZmZzZXQgPSB1LlZPUikKCiMgcHJpbnQKVk9SLm9mZnNldCRWb3Jvbm9pLnJlZ2lvbnMucGxvdAoKIyBzYXZlCmdnc2F2ZSgiUGxvdHMvVk9SX3JlZ2lvbnNfb2Zmc2V0LnBuZyIsIFZPUi5vZmZzZXQkVm9yb25vaS5yZWdpb25zLnBsb3QsIGRldmljZSA9ICJwbmciKQpgYGAKClRoZSB0aGlyZCBvcHRpb24gaXMgYWxzbyBvbiB0aGUgYW50ZW5uYSBsZXZlbCBhbmQgdXNlcyB0aGUgYmFyeWNlbnRlciBvZiBlYWNoIGFudGVubmEgYXMgc2VlZCBsb2NhdGlvbiBmb3IgdGhlIGNvbXB1dGF0aW9uIG9mIFZvcm9ub2kgcmVnaW9ucy4KCmBgYHtyIHZvci1iYXJ5Y2VudGVyLCBmaWcuY2FwPSJWb3Jvbm9pIHJlZ2lvbnMgd2l0aCBhbnRlbm5hIGJhcnljZW50ZXJzIGFzIHNlZWRzIn0KIyBWb3Jvbm9pIGVzdGltYXRpb24gd2l0aCBjZWxsIGJhcnljZW50ZXIgbG9jYXRpb25zIGFzIHNlZWRzClZPUi5iYXJ5Y2VudGVyIDwtIFZPUl9lc3QoYXJlYSA9IGFyZWEsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGxwbGFuLmNvbWJpbmVkID0gY2VsbHBsYW4uY29tYmluZWQsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHNpZ25hbC5zdHJlbmd0aC5jb21iLmR0ID0gc2lnbmFsLnN0cmVuZ3RoLmNvbWIuZHQsIAogICAgICAgICAgICAgICAgICAgICAgICAgIEMudmVjLmRmID0gQy52ZWMuZGYsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAiY2VsbC5iYXJ5Y2VudGVyIikgClZPUi5iYXJ5Y2VudGVyLmVzdCA8LSBWT1IuYmFyeWNlbnRlciRzZWVkLnZvcm9ub2kuZmluYWwgJT4lIAogIHJlbmFtZSh1LlZPUi5iYXJ5Y2VudGVyID0gdS5WT1IpCgojIHByaW50ClZPUi5iYXJ5Y2VudGVyJFZvcm9ub2kucmVnaW9ucy5wbG90CgojIHNhdmUKZ2dzYXZlKCJQbG90cy9WT1JfcmVnaW9uc19iYXJ5Y2VudGVyLnBuZyIsIFZPUi5iYXJ5Y2VudGVyJFZvcm9ub2kucmVnaW9ucy5wbG90LCBkZXZpY2UgPSAicG5nIikKYGBgCgojIyBFc3RpbWF0aW9uOiBTaW1wbGUgQmF5ZXMKClRoZSBTaW1wbGUgQmF5ZXMgKFNCKSBlc3RpbWF0b3IgbmVlZHMgdGhyZWUgcGFyYW1ldGVycywgdGhlIGMtdmVjdG9yLCBhbiBjb25uZWN0aW9uIHByb2JhYmlsaXR5IG1vZGVsIChQXCopIGFuZCBhIHByaW9yIHZlY3Rvci4gVGhlIGZvbGxvd2luZyBjdXN0b20gUi1mdW5jdGlvbiAoYEVNX2VzdCgpYCkgaXMgYmFzaWNhbGx5IHRoZSBzYW1lIGZvciB0aGUgTUxFIFBvaXNzb24gYW5kIFNCIGVzdGltYXRvci4gVGhlIG9ubHkgZGlmZmVyZW5jZSBpcyB0aGF0IHRoZSBTQiBlc3RpbWF0b3IgdXNlcyBvbmx5IG9uZSAoaS5lLiB0aGUgZmlyc3QpIGl0ZXJhdGlvbi4gRnVydGhlcm1vcmUsIHRoaXMgZnVuY3Rpb24gY29udGFpbnMgYSBwYXJhbWV0ZXIgYGxkdGAsIHdoaWNoIGNhbiBwb3RlbnRpYWxseSByZWR1Y2UgdGhlIGl0ZXJhdGlvbnMgdW50aWwgY29udmVyZ2VuY2UuIEl0IGNsaXBzIGFsbCBlc3RpbWF0ZXMgYmVsb3cgdGhpcyB2YWx1ZSB0byBleGFjdGx5IDAgYXMgdGhlIG9yaWdpbmFsIGVzdGltYXRvciBjb3VsZCBub3QgcmVhY2ggZXhhY3RseSAwLiBNb3N0IHBhcmFtZXRlcnMgYXJlIGFzc3VtZWQgb24gdGhlIGBkYXRhdGFibGVgIGZvcm1hdCBmb3IgY29tcHV0YXRpb25hbCBzcGVlZCBwdXJwb3Nlcy4KCmBgYHtyIFNCLWVzdGltYXRvcn0KIyBjYWNsdWxhdGUgU0IgZXN0aW1hdG9yClNCLmVzdCA8LSBFTV9lc3QoYy52ZWMuZHQgPSBjLnZlYy5kdCwgCiAgICAgICAgICAgICAgICAgUC5kdCA9IFAuc3Rhci5kdCwgCiAgICAgICAgICAgICAgICAgYS52ZWMuZHQgPSBhLnRpbGUuZHQsIAogICAgICAgICAgICAgICAgIHNlbGVjdGVkLnJhbmdlID0gMSwKICAgICAgICAgICAgICAgICBuLml0ZXIgPSAxLAogICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGLCAKICAgICAgICAgICAgICAgICBsZHQgPSAxMF4tMDQpICU+JSAKICBkcGx5cjo6cmVuYW1lKHRpbGUuaWQgPSBqLCBwcmlvci5TQiA9IGkudSwgdS5TQiA9IHUxKQpgYGAKCiMjIEVzdGltYXRpb246IE1MRSBQb2lzc29uCgpUaGUgTUxFIFBvaXNzb24gZXN0aW1hdG9yIGFzc3VtZXMgdGhlIHNhbWUgcGFyYW1ldGVycyBhcyB0aGUgU0IgZXN0aW1hdG9yLCBwbHVzIHRoZSBudW1iZXIgb2YgaXRlcmF0aW9ucyBpdCBzaG91bGQgcnVuLiBGb3IgY29udmVuaWVuY2UsIGBFTV9lc3QoKWAgY29udGFpbnMgdGhlIGFyZ3VtZW50IGBzZWxlY3RlZC5yYW5nZWAsIHdoaWNoIGdpdmVzIHRoZSB1c2VyIHRoZSBvcHRpb24gdG8gc3BlY2lmeSB3aGljaCBpdGVyYXRpb25zIHNob3VsZCBiZSBkZWxpdmVyZWQgaW4gdGhlIG91dHB1dCBkYXRhIGZyYW1lLgoKYGBge3IgTUxFLVBvaXNzb24tZXN0aW1hdG9yfQojIGRlZmluZSBudW1iZXIgb2YgaXRlcmF0aW9ucyAKbi5pdGVyLk1MRSA8LSAyMDAKCiMgVGltZSBsb2cKTUxFLnRpbWUgPC0gc3lzdGVtLnRpbWUoewogICMgY2FsY3VsYXRlIE1MRSBQb2lzc29uIGVzdGltYXRvcgogIE1MRS5lc3QgPC0gRU1fZXN0KGMudmVjLmR0ID0gYy52ZWMuZHQsIAogICAgICAgICAgICAgICAgICAgIFAuZHQgPSBQLnN0YXIuZHQsIAogICAgICAgICAgICAgICAgICAgIGEudmVjLmR0ID0gYS50aWxlLmR0LCAKICAgICAgICAgICAgICAgICAgICBzZWxlY3RlZC5yYW5nZSA9IGMoMToxMCwgc2VxKDIwLCA5MCwgMTApLCBzZXEoMTAwLCAyMDAsIDUwKSksCiAgICAgICAgICAgICAgICAgICAgbi5pdGVyID0gbi5pdGVyLk1MRSwKICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRiwgCiAgICAgICAgICAgICAgICAgICAgbGR0ID0gMTBeLTA0KSAlPiUgCiAgICByZW5hbWVfd2l0aCguZm4gPSB+Z3N1YigidSIsICJ1Lk1MRSIsIHggPSAueCwgZml4ZWQgPSBUKSwgCiAgICAgICAgICAgICAgICAuY29scyA9IHN0YXJ0c193aXRoKCJ1IikpICU+JSAKICAgIHJlbmFtZSh0aWxlLmlkID0gaiwgcHJpb3IuTUxFID0gaS51KQp9KQoKYGBgCgpPbmUgTUxFIGl0ZXJhdGlvbiB0YWtlcyBmb3IgdGhpcyB0b3l3b3JsZCBhbmQgdGhlIHNwZWNpZmllZCBjb25maWd1cmF0aW9ucyBvbiBhdmVyYWdlIGNhLiBgciBwYXN0ZShyb3VuZChNTEUudGltZVtbM11dIC8gbi5pdGVyLk1MRSwgMCkpYCBzZWNvbmRzLgoKIyMgRXN0aW1hdGlvbjogREYKClRoZSBERiBlc3RpbWF0b3IgYWxzbyBhc3N1bWVzIHRoZSBzYW1lIHBhcmFtZXRlcnMgYXMgdGhlIFNCIGFuZCBNTEUtUG9pc3NvbiBlc3RpbWF0b3IsIGhvd2V2ZXIsIGl0IGlzIGNvbXB1dGVkIG9uIHRoZSBzdXBlcnRpbGUgbGV2ZWwuIFRoZSBuZWNlc3NhcnkgbWF0cml4IGludmVyc2lvbiBpcyBjb21wdXRlZCB2aWEgdGhlIE1vb3JlIFBlbnJvc2UgaW52ZXJzZS4gR2l2ZW4gdGhlIGZhY3QgdGhhdCB0aGUgcmF3IERGIGVzdGltYXRlIGNhbiBjb250YWluIG5lZ2F0aXZlIHZhbHVlcywgaXQgaXMgYWRqdXN0ZWQgYW5kIHJlbm9ybWFsaXplZCBpbiB0aGUgZm9sbG93aW5nIHdheTogQWxsIGVzdGltYXRlIHZhbHVlcyBiZWxvdyAxIGFyZSBjbGlwcGVkIGF0IDEgYW5kIHRoZW4gb25lIGl0ZXJhdGlvbiB3aXRoIGBFTV9lc3QoKWAgaXMgY29uZHVjdGVkIGluIG9yZGVyIHRvIGFzc3VyZSB0aGF0IHRoZSBmaW5hbCBlc3RpbWF0ZXMgYXJlIG9mIHRoZSBzYW1lIG1hc3MgYXMgdGhlIGMtdmVjdG9yLiBUaGVyZWZvcmUsIHRoZSBERiBlc3RpbWF0b3IgaXMgdGhlIHJhdyBERiBlc3RpbWF0ZSBhZnRlciBvbmUgaXRlcmF0aW9uIChERjEpLgoKQXMgYW5vdGhlciBjb25maWd1cmF0aW9uLCB0aGlzIG5vdGVib29rIGNvbnRhaW5zIGFuIGFkanVzdGVkIE1MRSBQb2lzc29uIGVzdGltYXRvciwgd2hlcmUgdGhlIHByaW9yIHZlY3RvciBpcyB0aGUgYWRqdXN0ZWQgYW5kIHJlbm9ybWFsaXplZCBERiBlc3RpbWF0ZSB2ZWN0b3IuIFRoaXMgaXMgY29pbmVkIGluIHRoZSBmb2xsb3dpbmcgYXMgREYyMDAgd2hpY2ggbWVhbnMgdGhhdCBjb250YWlucyB0aGUgZXN0aW1hdGVzIGFmdGVyIDIwMCBpdGVyYXRpb25zLgoKYGBge3IgREYtZXN0aW1hdG9yfQojIFRpbWUgbG9nCkRGLnJhdy50aW1lIDwtIHN5c3RlbS50aW1lKHsKICAjIGNhbGN1bGF0ZSByYXcgREYgZXN0aW1hdGVzIHdpdGggc3VwZXJ0aWxlcwogIERGLnJhdy5lc3QgPC0gREZfZXN0KGMudmVjLmR0ID0gYy52ZWMuZHQsIAogICAgICAgICAgICAgICAgICAgICAgIFAuc3Rhci5zcG0gPSBQLnN0YXIuc3VwZXJ0aWxlLnNwbSwgCiAgICAgICAgICAgICAgICAgICAgICAgYS5zdXBlcnRpbGUudmVjID0gYS5zdXBlcnRpbGUuaGVscGVyKQogICMgYWRqdXN0IHJhdyBERiBlc3RpbWF0ZSAoY2xpcCkKICBERi5yYXcuZXN0LmR0IDwtIGRhdGEudGFibGUoaiA9IGFzLm51bWVyaWMobmFtZXMoYS5zdXBlcnRpbGUuaGVscGVyKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHUgPSBERi5yYXcuZXN0KSAlPiUKICAgIC5bLCB1IDo9IGZpZmVsc2UodSA8IDEsIDEsIHUpXSAjIGNsaXAgbG93ZXIgdmFsdWVzIHRoYW4gMSB0byAxCn0pCgojIyBSZW5vcm1hbGl6aW5nIHdpdGggRU0gYW5kIGJyaW5naW5nIGVzdGltYXRlIG9uIHJlZ3VsYXIgdGlsZS5pZCBsZXZlbApERi5lc3QgPC0gRU1fZXN0KGMudmVjLmR0ID0gYy52ZWMuZHQsIAogICAgICAgICAgICAgICAgIFAuZHQgPSBQLnN0YXIuc3VwZXJ0aWxlLmR0LCAKICAgICAgICAgICAgICAgICBhLnZlYy5kdCA9IERGLnJhdy5lc3QuZHQsCiAgICAgICAgICAgICAgICAgc2VsZWN0ZWQucmFuZ2UgPSBjKDE6MTAsIHNlcSgyMCwgOTAsIDEwKSwgc2VxKDEwMCwgMjAwLCA1MCkpLAogICAgICAgICAgICAgICAgIG4uaXRlciA9IG4uaXRlci5NTEUsCiAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEYsCiAgICAgICAgICAgICAgICAgbGR0ID0gMTBeLTA0KSAlPiUgCiAgcmVuYW1lX3dpdGgoLmZuID0gfmdzdWIoInUiLCAidS5ERiIsIHggPSAueCwgZml4ZWQgPSBUKSwgCiAgICAgICAgICAgICAgLmNvbHMgPSBzdGFydHNfd2l0aCgidSIpKSAlPiUgCiAgcmVuYW1lKHN1cGVydGlsZS5pZC5udW0gPSBqLCBwcmlvci5ERiA9IGkudSkgJT4lCiAgcmlnaHRfam9pbihhcmVhJGFyZWEuZGYuY29tcGxldGUsIGJ5ID0gInN1cGVydGlsZS5pZC5udW0iKSAlPiUgCiAgZ3JvdXBfYnkoc3VwZXJ0aWxlLmlkKSAlPiUgCiAgbXV0YXRlKGFjcm9zcyhzdGFydHNfd2l0aCgidSIpLCB+IC4gLyBuKCkpKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBkcGx5cjo6c2VsZWN0KHRpbGUuaWQsIHN0YXJ0c193aXRoKCJ1IikpCmBgYAoKQ29tcHV0aW5nIHRoZSByYXcgREYtZXN0aW1hdGUgKHJlbm9ybWFsaXplZCBidXQgd2l0aG91dCBmdXJ0aGVyIE1MRSBpdGVyYXRpb25zKSB0YWtlcyBmb3IgdGhpcyB0b3l3b3JsZCBhbmQgdGhlIHNwZWNpZmllZCBjb25maWd1cmF0aW9ucyBjYS4gYHIgcGFzdGUocm91bmQoREYucmF3LnRpbWVbWzNdXSArIChNTEUudGltZVtbM11dIC8gbi5pdGVyLk1MRSksIDApKWAgc2Vjb25kcy4KCiMgRXZhbHVhdGlvbgoKVGhlIGxhc3QgcGFydCBvZiB0aGlzIG5vdGVib29rIGZvbGxvd3MgdGhyb3VnaCB3aXRoIGV2YWx1YXRpbmcgdGhlIGRpZmZlcmVudCBlc3RpbWF0b3JzIGJhc2VkIG9uIHRoZSBmb2xsb3dpbmcgaW5kaWNhdG9yczogMWQgZGVuc2l0eSwgMmQgZGVuc2l0eSwgU3BhdGlhbCBkZW5zaXR5IChpLmUuLCBnZW9ncmFwaGljYWwgbWFwcyksIEtXRCwgYW5kIENvbnZlcmdlbmNlIGJhc2VkIG9uIEtXRC4KCiMjIEV2YWx1YXRpb246IFNldHVwCgpgYGB7ciBldmFsdWF0aW9uLXNldHVwfQojIHB1dHRpbmcgZXZlcnl0aGluZyBpbnRvIGFuIHNmLWRhdGFmcmFtZSB0b2dldGhlcgpmaW5hbC5lc3RpbWF0ZXMuc2YgPC0gYXJlYSRhcmVhLnNmICU+JSAKICBsZWZ0X2pvaW4oVk9SLnRvd2VyLmVzdCwgYnkgPSAidGlsZS5pZCIpICU+JSAKICBsZWZ0X2pvaW4oVk9SLm9mZnNldC5lc3QsIGJ5ID0gInRpbGUuaWQiKSAlPiUgCiAgbGVmdF9qb2luKFZPUi5iYXJ5Y2VudGVyLmVzdCwgYnkgPSAidGlsZS5pZCIpICU+JSAKICBsZWZ0X2pvaW4oU0IuZXN0LCBieSA9ICJ0aWxlLmlkIikgJT4lCiAgbGVmdF9qb2luKE1MRS5lc3QsIGJ5ID0gInRpbGUuaWQiKSAlPiUgCiAgbGVmdF9qb2luKERGLmVzdCwgYnkgPSAidGlsZS5pZCIpCgojIG5vbi1zZiB2ZXJzaW9uCmZpbmFsLmVzdGltYXRlcy5kZiA8LSBmaW5hbC5lc3RpbWF0ZXMuc2YgJT4lIAogIHN0X2Ryb3BfZ2VvbWV0cnkoKQoKIyB2ZWN0b3Igd2l0aCBuYW1lcyBvZiB0aGUgcmVsZXZhbnQgZXN0aW1hdGVzCm5hbWVzLmVzdGltYXRlcyA8LSBmaW5hbC5lc3RpbWF0ZXMuc2YgJT4lIAogICMgZHBseXI6OnNlbGVjdChwb3AsIHN0YXJ0c193aXRoKCJ1LiIpKSAlPiUgIyBhbGwgZXN0aW1hdGVzCiAgZHBseXI6OnNlbGVjdChwb3AsIG1hdGNoZXMoIlZPUnx1LlNCfERGMSR8MjAwIikpICU+JSAjIG9ubHkgImZpbmFsIiBvbmVzIGZvciBtYXBwaW5nCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSAKICBuYW1lcygpIApgYGAKCiMjIEV2YWx1YXRpb246IDFkIERlbnNpdHkKCmBgYHtyIDFkLWRlbnNpdHksIHdhcm5pbmc9RkFMU0UsIGZpZy5jYXA9IkNvbXBhcmluZyBFQ0NERiBvZiBlc3RpbWF0ZXMgdG8gdGhlIEdUUCJ9CiMgY2FsY3VsYXRlIGRlbnNpdHkgZGF0YXNldCBmb3IgYWxsIGVzdGltYXRlcyBhbmQgR1RQIApjZGYuY29tcGFyZSA8LSBmaW5hbC5lc3RpbWF0ZXMuZGYgJT4lIAogIGRwbHlyOjpzZWxlY3QodGlsZS5pZCwgYWxsX29mKG5hbWVzLmVzdGltYXRlcykpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IC10aWxlLmlkLCBuYW1lc190byA9ICJlc3RpbWF0ZXMiLCB2YWx1ZXNfdG8gPSAidmFsdWVzIikgJT4lIAogIHNwbGl0KC4kZXN0aW1hdGVzKSAlPiUgCiAgbWFwKH5jdXN0b21fZWNkZl9wcmVwKC4pKSAlPiUgCiAgbWFwKH5kcGx5cjo6c2VsZWN0KC4sIGN1bS5wcm9iLmNvbXAsIHBvcC5wbG90KSkgJT4lCiAgbWFwKH5tdXRhdGUoLiwgY3VtLnByb2IuY29tcCA9IHJvdW5kKGN1bS5wcm9iLmNvbXAsIDMpKSkgJT4lICMgZWZmZWN0aXZlIHBsb3Qgc2FtcGxlIC0tPiBmYXN0ZXIgcGxvdHRpbmcgZXhjbHVkaW5nIG92ZXJwbG90CiAgbWFwX2Rmcih+ZGlzdGluY3QoLiksIC5pZCA9ICJ0eXBlIikKIyMgV2FybmluZyBpbiBtYXNrJGV2YWxfYWxsX211dGF0ZShxdW8pOiBOQU5zIGNhbiBiZSBwcmVzZW50CgoKIyBFQ0NERiBwbG90CihFQ0NERi5wb3AucGxvdCA8LSBjZGYuY29tcGFyZSAlPiUgCiAgICBnZ3Bsb3QoKSArIAogICAgZ2VvbV9saW5lKGFlcyh4ID0gcG9wLnBsb3QsIHkgPSBjdW0ucHJvYi5jb21wLAogICAgICAgICAgICAgICAgICBjb2xvciA9IHR5cGUpLCBzaXplID0gMSkgKyAKICAgIHNjYWxlX2NvbG9yX3B0b2woKSArCiAgICBzY2FsZV95X2xvZzEwKGxhYmVscyA9IHNjYWxlczo6dHJhbnNfZm9ybWF0KCJsb2cxMCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZXM6Om1hdGhfZm9ybWF0KDEwXi54KSksCiAgICAgICAgICAgICAgICAgIG1pbm9yX2JyZWFrcyA9IG1pbm9yLmJyZWFrcykgKwogICAgc2NhbGVfeF9sb2cxMChsYWJlbHMgPSBzY2FsZXM6OnRyYW5zX2Zvcm1hdCgibG9nMTAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVzOjptYXRoX2Zvcm1hdCgxMF4ueCkpLAogICAgICAgICAgICAgICAgICBtaW5vcl9icmVha3MgPSBtaW5vci5icmVha3MpICsKICAgIGFubm90YXRpb25fbG9ndGlja3Moc2lkZXMgPSAibGIiKSArCiAgICBsYWJzKHRpdGxlID0gIkVDQ0RGIG9mIGVzdGltYXRvcnMgYW5kIEdUUCIsIHkgPSAibG9nMTAoRUNDREYpIiwgeCA9ICJsb2cxMChNb2JpbGUgcGhvbmVzKSIsICAKICAgICAgICAgY29sb3VyID0gIiIpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSkKCmdnc2F2ZSgiUGxvdHMvZWNjZGYuZXN0aW1hdGVzLnBuZyIsIEVDQ0RGLnBvcC5wbG90LCBkZXZpY2UgPSAicG5nIikKYGBgCgojIyBFdmFsdWF0aW9uOiAyZCBEZW5zaXR5CgpgYGB7ciAyZC1kZW5zaXR5LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQojIG5hbWVzIG9mIHNwZWNpZmllZCBlc3RpbWF0b3JzIGZvciBvcmRlciBjb250cm9sCm5hbWVzLm9yZGVyLmVzdGltYXRvciA8LSBjKCJ1LmZsYXQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgInUuVk9SLnRvd2VyIiwgInUuVk9SLm9mZnNldCIsICJ1LlZPUi5iYXJ5Y2VudGVyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgInUuU0IiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAidS5NTEUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgInUuREYiKQoKIyBkZWZpbmUgYWRkaXRpb25hbCBuZWNlc3NhcnkgcmVzY2FsaW5ncyBvZiB0aGUgYXJlYSAobmV4dCB0byAxeDEpCnJlc2NhbGUuZmFjdG9yLmxpc3QgPC0gbGlzdChhcmVhLjF4MSA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcmVhLjJ4MiA9IDIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYS40eDQgPSA0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFyZWEuOHg4ID0gOCkKCiMgYWdncmVnYXRlIGVzdGltYXRlIHZhbHVlcyBiYXNlZCBvbiByZXNjYWxpbmcgbGV2ZWwKYXJlYS5yZXNjYWxlZC5ncmlkIDwtIG1hcChyZXNjYWxlLmZhY3Rvci5saXN0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICB+c3RfbWFrZV9ncmlkKGFyZWEkYXJlYS5zZiwgY2VsbHNpemUgPSBhcmVhJGFyZWEucGFyYW1zW1siYmFzZS50aWxlLnNpemUiXV0gKiAueCkpCgojIGRlZmluZSByZWxldmFudCBlc3RpbWF0ZXMKbXNlLnJlbGV2YW50LmVzdGltYXRvcnMgPC0gZmluYWwuZXN0aW1hdGVzLnNmICU+JSAKICBkcGx5cjo6c2VsZWN0KHRpbGUuaWQsIHBvcCwgbWF0Y2hlcygiVk9SfFNCfGZsYXR8MXwxMHwxMDB8MjAwIikpICU+JSAKICBkcGx5cjo6c2VsZWN0KC1tYXRjaGVzKCJwcmlvciIpKQoKIyBkZXZlbG9wIGEgbGlzdCBlbGVtZW50IHdpdGggZ3JpZCBhZ2dyZWdhdGVkIHZhbHVlcwptc2UuZXN0IDwtIG1hcChhcmVhLnJlc2NhbGVkLmdyaWQsIH5hZ2dyZWdhdGUobXNlLnJlbGV2YW50LmVzdGltYXRvcnMsIGJ5ID0gLngsIEZVTiA9IG1lYW4sIGpvaW4gPSBzdF9jb250YWlucykpICU+JSAKICBtYXAofnN0X2Ryb3BfZ2VvbWV0cnkoLikpCgojIGRlZmluZSBkYXRhIGZyYW1lIHdpdGggdGhlIG5lY2Vzc2FyeSB2YXJpYWJsZXMgZm9yIHZpc3VhbGl6YXRpb24gb2YgZXN0aW1hdGUgdnMuIEdUUApwb2ludCA8LSBtc2UuZXN0ICU+JSAKICBtYXAofm11dGF0ZSguLCB0aWxlLmlkLnJlc2NhbGVkID0gcm93X251bWJlcigpKSkgJT4lIAogIG1hcCh+ZHBseXI6OnNlbGVjdCguLCB0aWxlLmlkLnJlc2NhbGVkLCBwb3AsIHN0YXJ0c193aXRoKCJ1LiIpKSkgJT4lIAogIG1hcF9kZnIofnBpdm90X2xvbmdlciguLCBjb2xzID0gLWModGlsZS5pZC5yZXNjYWxlZCwgcG9wKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWVzX3RvID0gImVzdGltYXRvciIsIHZhbHVlc190byA9ICJlc3RpbWF0ZSIpLCAuaWQgPSAic2NhbGUiKSAlPiUgCiAgbXV0YXRlKHJlc2NhbGUuZmFjdG9yID0gYXMubnVtZXJpYyhzdHJfZXh0cmFjdChzY2FsZSwgIltbOmRpZ2l0Ol1dIikpLAogICAgICAgICBpdGVyYXRpb24gPSBzdHJfZXh0cmFjdChlc3RpbWF0b3IsICJbWzpkaWdpdDpdXSsiKSwKICAgICAgICAga2luZCA9IHN0cl9leHRyYWN0KGVzdGltYXRvciwgIltbOmFscGhhOl1bOnB1bmN0Ol1dKyIpKSAlPiUgCiAgbXV0YXRlKGl0ZXJhdGlvbiA9IGNhc2Vfd2hlbihpcy5uYShpdGVyYXRpb24pIH4gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiBhcy5udW1lcmljKGl0ZXJhdGlvbikpKSAlPiUgCiAgbXV0YXRlKGVzdGltYXRvci5vcmRlcmVkID0gZmFjdG9yKGtpbmQsIGxldmVscyA9IG5hbWVzLm9yZGVyLmVzdGltYXRvcikpCgoKIyBzZWxlY3RlZCBlc3RpbWF0b3JzIChpdGVyYXRpb25zKSBmb3IgMmQgZGVuc2l0eSBwbG90cwpzY2F0dGVyLm5hbWVzIDwtIGMoInUuTUxFMSIsICJ1Lk1MRTEwIiwgInUuTUxFMTAwIiwgInUuTUxFMjAwIiwKICAgICAgICAgICAgICAgICAgICJ1LlZPUi5iYXJ5Y2VudGVyIiwgInUuVk9SLnRvd2VyIikKCiMgY3VzdG9tIDJkIGRlbnNpdHkgcGxvdHMKc2NhdHRlci5kZW5zaXR5LnBsb3RzIDwtIHNjYXR0ZXIubmFtZXMgJT4lIAogIG1hcCh+c2NhdHRlcl9kZW5zaXR5KHBvaW50LCBlc3RpbWF0b3IubmFtZSA9IC54KSkgJT4lCiAgc2V0X25hbWVzKHNjYXR0ZXIubmFtZXMpCgojIHByaW50CmdncHVicjo6YXNfZ2dwbG90KHNjYXR0ZXIuZGVuc2l0eS5wbG90cyR1Lk1MRTEpCmdncHVicjo6YXNfZ2dwbG90KHNjYXR0ZXIuZGVuc2l0eS5wbG90cyR1Lk1MRTEwKQpnZ3B1YnI6OmFzX2dncGxvdChzY2F0dGVyLmRlbnNpdHkucGxvdHMkdS5NTEUxMDApCmdncHVicjo6YXNfZ2dwbG90KHNjYXR0ZXIuZGVuc2l0eS5wbG90cyR1Lk1MRTIwMCkKZ2dwdWJyOjphc19nZ3Bsb3Qoc2NhdHRlci5kZW5zaXR5LnBsb3RzJHUuVk9SLmJhcnljZW50ZXIpCmdncHVicjo6YXNfZ2dwbG90KHNjYXR0ZXIuZGVuc2l0eS5wbG90cyR1LlZPUi50b3dlcikKCiMgc2F2ZQpnZ3NhdmUoIlBsb3RzL3UuTUxFMS4yZC5kZW5zaXR5LnBuZyIsIHNjYXR0ZXIuZGVuc2l0eS5wbG90cyR1Lk1MRTEsIGRldmljZSA9ICJwbmciKQpnZ3NhdmUoIlBsb3RzL3UuTUxFMTAuMmQuZGVuc2l0eS5wbmciLCBzY2F0dGVyLmRlbnNpdHkucGxvdHMkdS5NTEUxMCwgZGV2aWNlID0gInBuZyIpCmdnc2F2ZSgiUGxvdHMvdS5NTEUxMDAuMmQuZGVuc2l0eS5wbmciLCBzY2F0dGVyLmRlbnNpdHkucGxvdHMkdS5NTEUxMDAsIGRldmljZSA9ICJwbmciKQpnZ3NhdmUoIlBsb3RzL3UuTUxFMjAwLjJkLmRlbnNpdHkucG5nIiwgc2NhdHRlci5kZW5zaXR5LnBsb3RzJHUuTUxFMjAwLCBkZXZpY2UgPSAicG5nIikKZ2dzYXZlKCJQbG90cy91LlZPUi5iYXJ5Y2VudGVyLjJkLmRlbnNpdHkucG5nIiwgc2NhdHRlci5kZW5zaXR5LnBsb3RzJHUuVk9SLmJhcnljZW50ZXIsIGRldmljZSA9ICJwbmciKQpnZ3NhdmUoIlBsb3RzL3UuVk9SLnRvd2VyLjJkLmRlbnNpdHkucG5nIiwgc2NhdHRlci5kZW5zaXR5LnBsb3RzJHUuVk9SLnRvd2VyLCBkZXZpY2UgPSAicG5nIikKYGBgCgojIyBFdmFsdWF0aW9uOiBTcGF0aWFsIERlbnNpdHkKCmBgYHtyIHNwYXRpYWwtZXN0aW1hdGVzLW1hcHMsIG1lc3NhZ2U9RkFMU0V9CiMgZGVmaW5lIGxlZ2VuZCBsYWJlbHMgZm9yIG1hcHMKbWFwcy5sYWJlbHMgPC0gbGlzdCgiR1RQICAiLCAKICAgICAgICAgICAgICAgICAgICAiVk9SX3QiLCAiVk9SX28iLCAiVk9SX2IiLCAKICAgICAgICAgICAgICAgICAgICAiU0IgICAiLCAiTUxFICAiLCAiREYxICAiLCAiREYyMDAiKQoKIyBjaGVjayBpZiB0aGVyZSBpcyBkaXZlcmdlbmNlLCB3aGF0IGFyZSB0aGUgbWF4aW11bSBlc3RpbWF0ZXMgcGVyIHRpbGUgZm9yIGVhY2ggZXN0aW1hdG9yCm1heC5tYXBzIDwtIGZpbmFsLmVzdGltYXRlcy5zZiAlPiUgCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSAKICBkcGx5cjo6c2VsZWN0KHRpbGUuaWQsIHBvcCwgbWF0Y2hlcygiVk9SfHUuU0J8REYxJHwyMDAiKSkgJT4lICMgb25seSBzcGVjaWZpYyBvbmVzIGZvciBtYXBwaW5nCiAgc3VtbWFyaXNlX2FsbChtYXgpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IC10aWxlLmlkLCBuYW1lc190byA9ICJlc3RpbWF0b3IiLCB2YWx1ZXNfdG8gPSAiZXN0aW1hdGUiKQoKCiMgRGVmaW5lIGJyZWFrIHBvaW50cyBmb3IgZGlzY3JldGl6ZWQgc3BhdGlhbCBkZW5zaXR5IHBsb3RzCmJyZWFrcyA8LSBjKDAsIDIsIDUsIDEwLCAyMCwgNTAsIDEwMCwgMjAwLCAzNTAsIEluZikKbWFwcy5pbnB1dCA8LSBmaW5hbC5lc3RpbWF0ZXMuc2YgJT4lIAogIGRwbHlyOjpzZWxlY3QodGlsZS5pZCwgcG9wLCBhbGxfb2YobmFtZXMuZXN0aW1hdGVzKSkgJT4lIAogIG11dGF0ZShhY3Jvc3MoYyhwb3AsIHN0YXJ0c193aXRoKCJ1LiIpKSwgfmN1dCguLCBicmVha3MgPSBicmVha3MsIGRpZy5sYWIgPSA3LCByaWdodCA9IEYpKSkKCiMgQnVpbGQgbWFwcyBhbmQgcHJpbnQKKG1hcHMuZXN0aW1hdGlvbi5kZW5zaXR5IDwtIG5hbWVzLmVzdGltYXRlcyAlPiUKICAgIG1hcDIoLiwgbWFwcy5sYWJlbHMsIH5tYXBfZGVuc2l0eShkYXRhID0gbWFwcy5pbnB1dCwgdmFyID0gLngsIGxhYmVsID0gLnkpKSAlPiUKICAgIHNldF9uYW1lcyhuYW1lcy5lc3RpbWF0ZXMpKQoKIyBzYXZlCmdnc2F2ZSgiUGxvdHMvcG9wLm1hcC5wbmciLCBtYXBzLmVzdGltYXRpb24uZGVuc2l0eSRwb3AsIGRldmljZSA9ICJwbmciKQpnZ3NhdmUoIlBsb3RzL3UuVk9SLnRvd2VyLm1hcC5wbmciLCBtYXBzLmVzdGltYXRpb24uZGVuc2l0eSR1LlZPUi50b3dlciwgZGV2aWNlID0gInBuZyIpCmdnc2F2ZSgiUGxvdHMvdS5WT1Iub2Zmc2V0Lm1hcC5wbmciLCBtYXBzLmVzdGltYXRpb24uZGVuc2l0eSR1LlZPUi5vZmZzZXQsIGRldmljZSA9ICJwbmciKQpnZ3NhdmUoIlBsb3RzL3UuVk9SLmJhcnljZW50ZXIubWFwLnBuZyIsIG1hcHMuZXN0aW1hdGlvbi5kZW5zaXR5JHUuVk9SLmJhcnljZW50ZXIsIGRldmljZSA9ICJwbmciKQpnZ3NhdmUoIlBsb3RzL3UuU0IubWFwLnBuZyIsIG1hcHMuZXN0aW1hdGlvbi5kZW5zaXR5JHUuU0IsIGRldmljZSA9ICJwbmciKQpnZ3NhdmUoIlBsb3RzL3UuTUxFMjAwLm1hcC5wbmciLCBtYXBzLmVzdGltYXRpb24uZGVuc2l0eSR1Lk1MRTIwMCwgZGV2aWNlID0gInBuZyIpCmdnc2F2ZSgiUGxvdHMvdS5ERjEubWFwLnBuZyIsIG1hcHMuZXN0aW1hdGlvbi5kZW5zaXR5JHUuREYxLCBkZXZpY2UgPSAicG5nIikKZ2dzYXZlKCJQbG90cy91LkRGMjAwLm1hcC5wbmciLCBtYXBzLmVzdGltYXRpb24uZGVuc2l0eSR1LkRGMjAwLCBkZXZpY2UgPSAicG5nIikKCmBgYAoKIyMgRXZhbHVhdGlvbjogS1dECgpDb21wdXRpbmcgdGhlIEthbnRvcm92aXRjaCBXYXNzdGVyc3RlaW4gRGlzdGFuY2UgKEtXRCksIGEuay5hLiBFYXJ0aCBNb3ZlcnMgRGlzdGFuY2UsIGlzIGRvbmUgYnkgdXNpbmcgdGhlIHBhY2thZ2Ugb2YgUHJvZi4gU3RlZmFubyBHdWFsYW5kaSBgU3BhdGlhbEtXRGAuIFRoaXMgcGFja2FnZSBpcyB0aGUgcmVzdWx0IG9mIHRoZSBhcnRpY2xlIHRoYXQgaXMgcmVmZXJlbmNlZCBbaGVyZV0oaHR0cHM6Ly9lcHVicy5zaWFtLm9yZy9kb2kvYWJzLzEwLjExMzcvMTlNMTI2MTE5NT9jYXNhX3Rva2VuPWYybmVrMXRtWHRnQUFBQUElM0FrdXdUU25DZzhFVEJOQmlhemdnZFBjcXhCeWNmMDV2OTRCczFyYmFkTEVUbU1nazVvNVE3X0R4VEl6MTVXWXhQY1ZXZ3YydnJ3USYpLCB3aGljaCBkZXZlbG9wcyBhbiBjb21wdXRhdGlvbmFsbHkgZWZmaWNpZW50IGFwcHJveGltYXRpb24gbWV0aG9kIG9mIHRoZSBLV0QuIFRoZSBwYWNrYWdlIG9mZmVycyB0aGUgdmVyeSBjb252ZW5pZW50IGZ1bmN0aW9uIGBjb21wYXJlT25lVG9NYW55KClgLCB3aGljaCBhc3N1bWVzIHRoZSBtYWluIHBhcmFtZXRlcnMgYENvb3JkaW5hdGVzYCwgYFdlaWdodHNgIGFuZCBgTGAuIFRoZSBmaXJzdCBkZXNjcmliZXMgdGhlIHBvaW50IGNvb3JkaW5hdGVzIG9mIGVhY2ggdGlsZSwgZm9yIHdoaWNoIHdlIHVzZSB0aGUgY2VudHJvaWQgb2YgZWFjaCB0aWxlLCB0aGUgc2Vjb25kIHVzZXMgdGhlIEdUUCBhbmQgdGhlIGVzdGltYXRlcywgdGhlIHRoaXJkIHBhcmFtZXRlciBzcGVjaWZpZXMgdGhlIGFwcHJveGltYXRpb24sIGkuZS4gdGhlIGFjY3VyYWN5IG9mIHRoZSBhcHByb3hpbWF0ZWQgS1dELiBIZXJlLCB3ZSB1c2UgdGhlIGRlZmF1bHQgKGBMID0gM2ApLCB3aGljaCBiYXNpY2FsbHkgbWVhbnMgdGhhdCB0aGUgYXBwcm94aW1hdGlvbiBlcnJvciB3aWxsIGJlIGluIHRoZSB3b3JzdCBjYXNlIDEuMjklLiBUaGUgb3V0cHV0IHZhbHVlIGlzIHVzZWQgYXMgYW4gdXBwZXIgYm91bmQgZm9yIHRoZSBLV0QgZXN0aW1hdGUgYW5kIHRoZSB3b3JzdCBjYXNlIGFkanVzdG1lbnQgYWN0cyBhcyBhIGxvd2VyIGJvdW5kLgoKVGhpcyBmdW5jdGlvbiBpcyBwYXJ0aWN1bGFybHkgY29udmVuaWVudCwgYXMgaXQgYWxsb3dzIHRvIGltcGxlbWVudCB0aGUgYFdlaWdodHNgIG9iamVjdCB0byBiZSBhIG1hdHJpeCwgaS5lLiBhcyBtYW55IGVzdGltYXRlcyBjYW4gYmUgaW1wbGVtZW50ZWQgYXMgdGhlIHVzZXIgd2FudHMuIFRoZSBmaXJzdCBjb2x1bW4gaXMgYWx3YXlzIGFzc3VtZWQgdG8gYmUgdGhlIEdUUCwgd2hpY2ggYWxzbyBhY3RzIGFzIHRoZSByZWZlcmVuY2UuCgpgYGB7ciBrd2Qtc2V0dXB9CiMgZGV2ZWxvcCBkYXRhZnJhbWUgd2l0aCBHVFAsIGFsbCBlc3RpbWF0ZXMgYW5kIHRpbGUgY2VudHJvaWRzCmt3ZC5oZWxwZXIuZXN0IDwtIGZpbmFsLmVzdGltYXRlcy5zZiAlPiUgCiAgZHBseXI6OnNlbGVjdCgtYyhlbGV2YXRpb24sIHR5cGUpKSAlPiUKICBtdXRhdGUodS5mbGF0ID0gbWVhbihwb3ApKSAlPiUgCiAgbXV0YXRlKGxvbiA9IG1hcF9kYmwoZ2VvbWV0cnksIH5zdF9jZW50cm9pZCgueClbWzFdXSksCiAgICAgICAgIGxhdCA9IG1hcF9kYmwoZ2VvbWV0cnksIH5zdF9jZW50cm9pZCgueClbWzJdXSkpICU+JSAKICBzdF9kcm9wX2dlb21ldHJ5KCkKCiMgQ29vcmRpbmF0ZXMgb2JqZWN0CmNvb3JkaW5hdGVzIDwtIGt3ZC5oZWxwZXIuZXN0ICU+JSAKICBkcGx5cjo6c2VsZWN0KGxvbiwgbGF0KSAlPiUgCiAgYXMubWF0cml4KCkKCiMgV2VpZ2h0cyBvYmplY3QKd2VpZ2h0cyA8LSBrd2QuaGVscGVyLmVzdCAlPiUgCiAgZHBseXI6OnNlbGVjdChwb3AsIG1hdGNoZXMoInUuIikpICU+JSAKICBhcy5tYXRyaXgoKQoKIyBBcHByb3hpbWF0aW9uIHBhcmFtZXRlciAodGhlIGhpZ2hlciB0aGUgbW9yZSBhY2N1cmF0ZSkKTCA9IDMKCiMgUnVuIEtXRAprd2QuZmluYWwgPC0gY29tcGFyZU9uZVRvTWFueShjb29yZGluYXRlcywgd2VpZ2h0cywgTCA9IEwsIHJlY29kZSA9IFRSVUUpCnBhc3RlKCJLV0QgcnVudGltZSAoIEwgPSIsIEwsICIpOiIsIHJvdW5kKGt3ZC5maW5hbCRydW50aW1lIC8gNjAsIDApLCAibWluIGZvciIsIAogICAgICBuY29sKHdlaWdodHMpIC0gMSwgImVzdGltYXRlcyBhbmQiLCAKICAgICAgbGVuZ3RoKGZpbmFsLmVzdGltYXRlcy5kZiR0aWxlLmlkKSwgInRpbGVzIikKCiMgRGVmaW5lIG5hbWVzIGZvciBlc3RpbWF0b3JzCm5hbWVzLndlaWdodHMgPC0gY29sbmFtZXMod2VpZ2h0cylbLTFdCgojIERldmVsb3AgZGF0YSBmcmFtZSBvbiB0aGUgZXN0aW10b3IgbGV2ZWwgd2l0aCByZXNwZWN0aXZlIEtXRCB2YWx1ZXMKa3dkLmV2YWwgPC0gdGliYmxlKGVzdGltYXRvciA9IG5hbWVzLndlaWdodHMsCiAgICAgICAgICAgICAgICAgICBrd2QgPSBrd2QuZmluYWwkZGlzdGFuY2UgKiAxKSAlPiUgICMgcmVzY2FsaW5nIGFjY29yZGluZyB0byBzY2FsZQogIG11dGF0ZShrd2QubG93ZXIuYm91bmQgPSBrd2QgLSAoKGt3ZCAvIDEwMCkgKiAxLjI5KSkgJT4lICAjIGZvciBMPTMgd2l0aGluIDEgcGVyY2VudCAKICBtdXRhdGUoaXRlcmF0aW9uID0gc3RyX2V4dHJhY3QoZXN0aW1hdG9yLCAiW1s6ZGlnaXQ6XV0rIiksCiAgICAgICAgIGtpbmQgPSBzdHJfZXh0cmFjdChlc3RpbWF0b3IsICJbWzphbHBoYTpdWzpwdW5jdDpdXSsiKSkgJT4lCiAgbXV0YXRlKGl0ZXJhdGlvbiA9IGNhc2Vfd2hlbihpcy5uYShpdGVyYXRpb24pIH4gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiBhcy5udW1lcmljKGl0ZXJhdGlvbikpKSAlPiUgCiAgZ3JvdXBfYnkoa2luZCkgJT4lIAogIG11dGF0ZShtaW4ua3dkLmtpbmQgPSBtaW4oa3dkKSwKICAgICAgICAga2luZC5ncm91cCA9IHJvd19udW1iZXIoKSAvIG1heChyb3dfbnVtYmVyKCkpKSAlPiUgIyBmaW5kIG1pbmltdW0gcGVyIGVzdGltYXRvciAoZm9yIG9yZGVyaW5nIGhlbHApCiAgdW5ncm91cCgpICU+JSAKICBhcnJhbmdlKGRlc2MobWluLmt3ZC5raW5kKSwgaXRlcmF0aW9uKSAlPiUgCiAgbXV0YXRlKGZpbmFsLm9yZGVyID0gcm93X251bWJlcigpKSAlPiUgCiAgbXV0YXRlKGVzdGltYXRvci5uZXcgPSBmYWN0b3IoZmluYWwub3JkZXIsIGxhYmVscyA9IGVzdGltYXRvcikpCmBgYAoKYGBge3Iga3dkLWZpbmFsLWVzdGltYXRlcy1wbG90LCBmaWcuY2FwPSJLV0QgKEw9MykgdmFsdWVzIG9mIHRoZSBmaW5hbCBlc3RpbWF0ZXMifQojIGRlZmluZSB0aGUgZmxhdCBlc3RpbWF0b3IgS1dEIHZhbHVlIGZvciByZWZlcmVuY2UgcHVycG9zZXMKIyB0aGlzIGlzIG9ubHkgaW1wbGVtZW50ZWQgYXMgYSBjYXB0aW9uIGluIHRoZSBmb2xsb3dpbmcgcGxvdCB0byBwcmV2ZW50IHNjYWxlIGRpc3RvcnRpdG9uCmZsYXQucmVmIDwtIHJvdW5kKGFzLm51bWVyaWMoa3dkLmV2YWxba3dkLmV2YWwkZXN0aW1hdG9yID09ICJ1LmZsYXQiLCAia3dkIl0pLCAyKQoKIyBkZXZlbG9wIEtXRCBiYXIgcGxvdCBmb3Igc2VsZWN0ZWQgZXN0aW1hdG9ycwooa3dkLmZpbmFsLmVzdGltYXRlcy5wbG90IDwtIGt3ZC5ldmFsICU+JSAKICAgIGZpbHRlcihraW5kLmdyb3VwID09IDEgfCBlc3RpbWF0b3IgPT0gInUuREYxIiwKICAgICAgICAgICAhc3RyX2RldGVjdChlc3RpbWF0b3IsICJmbGF0fHByaW9yIikpICU+JSAKICAgIGdncGxvdChhZXMoeCA9IGVzdGltYXRvci5uZXcsIHkgPSBrd2QsIGZpbGwgPSBraW5kLCBhbHBoYSA9IGtpbmQuZ3JvdXApKSArIAogICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjkpKSArIAogICAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IGt3ZC5sb3dlci5ib3VuZCwgeW1heCA9IGt3ZCksIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjkpLCB3aWR0aCA9IDAuMjUpICsKICAgIGdlb21fdGV4dChhZXMoeCA9IGVzdGltYXRvci5uZXcsIHkgPSBrd2QsIGxhYmVsID0gcm91bmQoa3dkLCAyKSksIAogICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoMC4xKSwgaGp1c3QgPSAtMC4xLCBjb2xvciA9ICJCbGFjayIsIHNpemUgPSAzKSArCiAgICBzY2FsZV9hbHBoYShyYW5nZSA9IGMoMC41LCAxKSwgCiAgICAgICAgICAgICAgICBndWlkZSA9IEYKICAgICAgICAgICAgICAgICMgbGFiZWxzID0gdW5pcXVlKGt3ZC5ldmFsJGl0ZXJhdGlvbikKICAgICkgKyAKICAgIHNjYWxlX2ZpbGxfcHRvbChndWlkZSA9IEZBTFNFKSArCiAgICBjb29yZF9mbGlwKCkgKwogICAgbGFicyh4ID0gIiIsIHkgPSAiS1dEIiwgCiAgICAgICAgIGFscGhhID0gIkl0ZXJhdGlvbiIsIAogICAgICAgICBzdWJ0aXRsZSA9IHBhc3RlMCgiKFJlZmVyZW5jZTogRmxhdCA9ICIsIGZsYXQucmVmLCAiKSIpKSArIAogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpKQoKIyBzYXZlCmdnc2F2ZShmaWxlbmFtZSA9ICJQbG90cy9rd2QuZmluYWwuZXN0aW1hdGVzLnBsb3QucG5nIiwgcGxvdCA9IGt3ZC5maW5hbC5lc3RpbWF0ZXMucGxvdCwgZGV2aWNlID0gInBuZyIpCmBgYAoKIyMgRXZhbHVhdGlvbjogS1dEIGNvbnZlcmdlbmNlCgpgYGB7ciBrd2QtY29udmVyZ2VuY2UtcGxvdCwgZmlnLmNhcD0iQ29udmVyZ2VuY2UgYmVoYXZpb3Igb2YgYWRqdXN0ZWQgYW5kIHJlbm9ybWxhaXplZCBERiBhbmQgTUxFIFBvaXNzb24gZXN0aW1hdG9yIGluIHRlcm1zIG9mIEtXRCJ9CgojIGtlZXAgb25seSBpdGVyYXRlZCBlc3RpbWF0b3JzCmt3ZC5ldmFsLmNvbnZlcmdlbmNlIDwtIGt3ZC5ldmFsICU+JSAKICBmaWx0ZXIoIWl0ZXJhdGlvbiA9PSAwKQoKIyBkZXZlbG9wIGxpbmUgcGxvdAooa3dkLmNvbnZlcmdlbmNlLnBsb3QubG9nLnRleHQgPC0ga3dkLmV2YWwuY29udmVyZ2VuY2UgJT4lIAogICAgZHBseXI6OnNlbGVjdChlc3RpbWF0b3IsIGt3ZCwga3dkLmxvd2VyLmJvdW5kLCBpdGVyYXRpb24sIGtpbmQpICU+JSAKICAgIGdncGxvdChhZXMoeCA9IGl0ZXJhdGlvbiwgeSA9IGt3ZCkpICsKICAgIGdlb21fbGluZShhZXMoY29sb3IgPSBraW5kKSkgKwogICAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBraW5kKSkgKwogICAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBrd2QubG93ZXIuYm91bmQsIHltYXggPSBrd2QsIGdyb3VwID0ga2luZCksIGFscGhhID0gMC4yKSArCiAgICBnZW9tX3RleHRfcmVwZWwoYWVzKGNvbG9yID0ga2luZCwgbGFiZWwgPSByb3VuZChrd2QsIDIpKSwgc2l6ZSA9IDMsIHNob3cubGVnZW5kICA9IEYpICsKICAgIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gc2NhbGVzOjp0cmFuc19mb3JtYXQoImxvZzEwIiwgc2NhbGVzOjptYXRoX2Zvcm1hdCgxMF4ueCkpLAogICAgICAgICAgICAgICAgICBtaW5vcl9icmVha3MgPSBtaW5vci5icmVha3MpICsKICAgIGFubm90YXRpb25fbG9ndGlja3Moc2lkZXMgPSAiYiIpICsKICAgIGxhYnMoY29sb3IgPSAiRXN0aW1hdG9yIiwKICAgICAgICAgeCA9ICJJdGVyYXRpb24iLAogICAgICAgICB5ID0gIktXRCIpKQoKIyBzYXZlCmdnc2F2ZSgiUGxvdHMva3dkLmNvbnZlcmdlbmNlLnBuZyIsIGt3ZC5jb252ZXJnZW5jZS5wbG90LmxvZy50ZXh0LCBkZXZpY2UgPSAicG5nIikKCgpgYGAKCgpgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGAKCg==